2023-04-19 01:30:38 +01:00
|
|
|
package target
|
|
|
|
|
2023-04-20 02:53:43 +01:00
|
|
|
import (
|
2023-04-21 03:21:46 +01:00
|
|
|
"fmt"
|
|
|
|
"github.com/MrMelon54/violet/proxy"
|
|
|
|
"github.com/MrMelon54/violet/utils"
|
|
|
|
"github.com/rs/cors"
|
|
|
|
"log"
|
2023-04-20 02:53:43 +01:00
|
|
|
"net/http"
|
2023-04-21 03:21:46 +01:00
|
|
|
"net/url"
|
|
|
|
"path"
|
2023-04-24 15:36:21 +01:00
|
|
|
"strings"
|
2023-04-20 02:53:43 +01:00
|
|
|
)
|
|
|
|
|
2023-04-24 15:36:21 +01:00
|
|
|
// serveApiCors outputs the cors headers to make APIs work.
|
2023-04-21 03:21:46 +01:00
|
|
|
var serveApiCors = cors.New(cors.Options{
|
2023-05-29 00:01:57 +01:00
|
|
|
AllowedOrigins: []string{"*"}, // allow all origins for api requests
|
2023-04-21 03:21:46 +01:00
|
|
|
AllowedHeaders: []string{"Content-Type", "Authorization"},
|
|
|
|
AllowedMethods: []string{
|
|
|
|
http.MethodGet,
|
|
|
|
http.MethodHead,
|
|
|
|
http.MethodPost,
|
|
|
|
http.MethodPut,
|
|
|
|
http.MethodPatch,
|
|
|
|
http.MethodDelete,
|
|
|
|
http.MethodConnect,
|
|
|
|
},
|
|
|
|
AllowCredentials: true,
|
|
|
|
})
|
|
|
|
|
2023-04-24 15:36:21 +01:00
|
|
|
// Route is a target used by the router to manage forwarding traffic to an
|
|
|
|
// internal server using the specified configuration.
|
2023-04-19 01:30:38 +01:00
|
|
|
type Route struct {
|
2023-04-24 15:36:21 +01:00
|
|
|
Pre bool // if the path has had a prefix removed
|
|
|
|
Host string // target host
|
|
|
|
Port int // target port
|
|
|
|
Path string // target path (possibly a prefix or absolute)
|
|
|
|
Abs bool // if the path is a prefix or absolute
|
|
|
|
Cors bool // add CORS headers
|
|
|
|
SecureMode bool // use HTTPS internally
|
|
|
|
ForwardHost bool // forward host header internally
|
|
|
|
ForwardAddr bool // forward remote address
|
|
|
|
IgnoreCert bool // ignore self-cert
|
|
|
|
Headers http.Header // extra headers
|
|
|
|
Proxy http.Handler // reverse proxy handler
|
2023-04-21 03:21:46 +01:00
|
|
|
}
|
|
|
|
|
2023-04-24 15:36:21 +01:00
|
|
|
// IsIgnoreCert returns true if IgnoreCert is enabled.
|
2023-04-21 03:21:46 +01:00
|
|
|
func (r Route) IsIgnoreCert() bool { return r.IgnoreCert }
|
|
|
|
|
2023-04-24 15:36:21 +01:00
|
|
|
// UpdateHeaders takes an existing set of headers and overwrites them with the
|
|
|
|
// extra headers.
|
2023-04-21 03:21:46 +01:00
|
|
|
func (r Route) UpdateHeaders(header http.Header) {
|
|
|
|
for k, v := range r.Headers {
|
|
|
|
header[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-24 15:36:21 +01:00
|
|
|
// FullHost outputs a host:port combo or just the host if the port is 0.
|
2023-04-21 03:21:46 +01:00
|
|
|
func (r Route) FullHost() string {
|
|
|
|
if r.Port == 0 {
|
|
|
|
return r.Host
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%s:%d", r.Host, r.Port)
|
2023-04-19 01:30:38 +01:00
|
|
|
}
|
2023-04-20 02:53:43 +01:00
|
|
|
|
2023-04-24 15:36:21 +01:00
|
|
|
// ServeHTTP responds with the data proxied from the internal server to the
|
|
|
|
// response writer provided.
|
2023-04-20 02:53:43 +01:00
|
|
|
func (r Route) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
2023-04-21 03:21:46 +01:00
|
|
|
if r.Cors {
|
2023-04-24 15:36:21 +01:00
|
|
|
// wraps with CORS handler
|
2023-04-21 03:21:46 +01:00
|
|
|
serveApiCors.Handler(http.HandlerFunc(r.internalServeHTTP)).ServeHTTP(rw, req)
|
|
|
|
} else {
|
|
|
|
r.internalServeHTTP(rw, req)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-24 15:36:21 +01:00
|
|
|
// internalServeHTTP is an internal method which handles configuring the request
|
|
|
|
// for the reverse proxy handler.
|
2023-04-21 03:21:46 +01:00
|
|
|
func (r Route) internalServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
2023-04-24 15:36:21 +01:00
|
|
|
// set the scheme and port using defaults if the port is 0
|
2023-04-21 03:21:46 +01:00
|
|
|
scheme := "http"
|
|
|
|
if r.SecureMode {
|
|
|
|
scheme = "https"
|
|
|
|
if r.Port == 0 {
|
|
|
|
r.Port = 443
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if r.Port == 0 {
|
|
|
|
r.Port = 80
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-24 15:36:21 +01:00
|
|
|
// if not Abs then join with the ending of the current path
|
2023-04-21 03:21:46 +01:00
|
|
|
p := r.Path
|
|
|
|
if !r.Abs {
|
|
|
|
p = path.Join(r.Path, req.URL.Path)
|
2023-04-24 15:36:21 +01:00
|
|
|
|
|
|
|
// replace the trailing slash that path.Join() strips off
|
|
|
|
if strings.HasSuffix(req.URL.Path, "/") {
|
|
|
|
p += "/"
|
|
|
|
}
|
2023-04-21 03:21:46 +01:00
|
|
|
}
|
|
|
|
|
2023-04-24 15:36:21 +01:00
|
|
|
// fix empty path
|
2023-04-21 03:21:46 +01:00
|
|
|
if p == "" {
|
|
|
|
p = "/"
|
|
|
|
}
|
|
|
|
|
2023-04-24 15:36:21 +01:00
|
|
|
// create a new URL
|
2023-04-21 03:21:46 +01:00
|
|
|
u := &url.URL{
|
|
|
|
Scheme: scheme,
|
|
|
|
Host: r.FullHost(),
|
|
|
|
Path: p,
|
|
|
|
RawQuery: req.URL.RawQuery,
|
|
|
|
}
|
2023-04-24 15:36:21 +01:00
|
|
|
|
2023-05-29 00:01:57 +01:00
|
|
|
// close the incoming body after use
|
|
|
|
defer req.Body.Close()
|
|
|
|
|
2023-04-24 15:36:21 +01:00
|
|
|
// create the internal request
|
2023-05-29 00:01:57 +01:00
|
|
|
req2, err := http.NewRequest(req.Method, u.String(), req.Body)
|
2023-04-21 03:21:46 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("[ServeRoute::ServeHTTP()] Error generating new request: %s\n", err)
|
|
|
|
utils.RespondHttpStatus(rw, http.StatusBadGateway)
|
|
|
|
return
|
|
|
|
}
|
2023-04-24 15:36:21 +01:00
|
|
|
|
|
|
|
// loops over the incoming request headers
|
2023-04-21 03:21:46 +01:00
|
|
|
for k, v := range req.Header {
|
2023-04-24 15:36:21 +01:00
|
|
|
// ignore host header
|
2023-04-21 03:21:46 +01:00
|
|
|
if k == "Host" {
|
|
|
|
continue
|
|
|
|
}
|
2023-04-24 15:36:21 +01:00
|
|
|
// copy header into the internal request
|
2023-04-21 03:21:46 +01:00
|
|
|
req2.Header[k] = v
|
|
|
|
}
|
2023-04-24 15:36:21 +01:00
|
|
|
|
|
|
|
// if extra route headers are set
|
|
|
|
if r.Headers != nil {
|
|
|
|
// loop over headers
|
|
|
|
for k, v := range r.Headers {
|
|
|
|
// copy header into the internal request
|
|
|
|
req2.Header[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if forward host is enabled then send the host
|
2023-04-21 03:21:46 +01:00
|
|
|
if r.ForwardHost {
|
|
|
|
req2.Host = req.Host
|
|
|
|
}
|
2023-04-24 15:36:21 +01:00
|
|
|
if r.ForwardAddr {
|
|
|
|
req2.Header.Add("X-Forwarded-For", req.RemoteAddr)
|
|
|
|
}
|
|
|
|
|
|
|
|
// serve request with reverse proxy
|
2023-04-21 03:21:46 +01:00
|
|
|
r.Proxy.ServeHTTP(rw, proxy.SetReverseProxyHost(req2, r))
|
2023-04-20 02:53:43 +01:00
|
|
|
}
|
2023-04-24 15:36:21 +01:00
|
|
|
|
|
|
|
// String outputs a debug string for the route.
|
|
|
|
func (r Route) String() string {
|
|
|
|
return fmt.Sprintf("%#v", r)
|
|
|
|
}
|