From 1a70535a8fe2ffa7f2dfb56c9b02f71261e7c23d Mon Sep 17 00:00:00 2001 From: geosoft1 Date: Thu, 11 Apr 2019 13:37:56 +0300 Subject: [PATCH] - --- CHANGES | 3 ++ README.md | 91 ++++++++++++++++++++++++++++++++++------------- conf.json | 21 ++++++----- http.go | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++ https.go | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 95 +++++++++++++++++++++++++++++++++---------------- 6 files changed, 346 insertions(+), 66 deletions(-) create mode 100644 http.go create mode 100644 https.go diff --git a/CHANGES b/CHANGES index df9d1fb..182b491 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ 1.0.1 - some minor bugs corrections +2.0.0 +- code rewritten +- https server added \ No newline at end of file diff --git a/README.md b/README.md index a709ab3..a7e24f3 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,95 @@ reverseproxy ==== -[![version](https://img.shields.io/badge/version-1.0.1-blue.svg)](https://github.com/geosoft1/reverseproxy/archive/master.zip) +[![version](https://img.shields.io/badge/version-2.0.0-blue.svg)](https://github.com/geosoft1/reverseproxy/archive/master.zip) [![license](https://img.shields.io/badge/license-gpl-blue.svg)](https://github.com/geosoft1/reverseproxy/blob/master/LICENSE) Simple [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) server. Useful for accessing web applications on various servers (or VMs) through a single domain. -### How it works? +## How it works? ![reverseproxy](https://user-images.githubusercontent.com/6298396/36028867-5e549ea4-0da9-11e8-8ecf-62546e95ca5c.png) Just complete the `conf.json` file and run the server. Example: - { - "ip":"", - "port":"8080", - "routes":{ - "/upload":"192.168.88.160:8080", - "/Downloads/":"192.168.88.164:8000", - "#":"the pattern / matches all paths not matched by other registered patterns", - "/":"192.168.88.161" - } - } + { + "routes": { + "#": "the pattern / matches all paths not matched by other registered patterns", + "/": "http://192.168.88.250", + "/wrong": "192.168.88.250:8080", + "/upload": "http://192.168.88.250:8080", + "/hello": "https://192.168.88.250:8090", + "/static/": "http://192.168.88.250:8080", + "#/disabled": "192.168.88.250:8080" + } + } -## Configuration details +## Getting started - "ip":"", +To compile the reverse proxy server use -No ip mean `localhost` on hosting server. Is no need to change this. + go build - "port":"8080", +If you still want just an HTTP reverse proxy, compile with -The server listening on this port. Remeber to forward the port `80` to this port if your connection pass through a router. No root right are required if you run on big ports (eg. `8080`). + go build http.go + +or for HTTPS + + go build https.go + +Parameters + +### `-conf` + +Start program with a certain configuration file. Default `conf.json`. + +### `-http` + +Listening address and port for HTTP server. Default `:8080`. + +### `-https` + +Listening address and port for HTTPS server. Default `:8090`. + +### `-https-enabled` + +Enable HTTPS server. Default `false`. + +### `-verbose` + +Enable verbose mode for middleware. ## Routes Routes has the folowing structure - "path":"target" + "path":"host" + +The path is what you request and the host is what you get. The reverse proxy always add the path to the host (eg. if your host address is `example.com` then the path `/` mean `example.com/` and `/upload` mean `example.com/upload`). + +Paths starting with `#` are comments and are not added to routes. + +A path like `/name/` match any request starting with `name` (eg. `/api/` match also `/api/bla` and so on). + +Hosts must be a complete url address and port. + +Do not repeat the routes because the server will take always the last route to a host. + +## Testing the server -The path is what you request and the target is what you get (eg. if your domain is `example.com` then `/` mean `example.com/` and `/upload` mean `example.com/upload`). + curl --verbose http://localhost:8080/hello -`#` path mean a comment and is not added to routes. Put the text in target. `#something` don't mean a comment. +For HTTPS use -The reverse proxy add your path to the target, so be prepared to handle this path. For example the folowing will get an error page. + openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt + curl --insecure --verbose https://localhost:8090/hello - "/upload":"google.com" +## Faq -Use `/` path for main site which have index page on `/`. Use sufixes for other web services which have the sufix as main page. +### Why the HTTPS is not enabled by default? -Remeber that a route like `/name/` mean match any starting with `name` (eg. `/api/` match also `/api/bla` and so on). +HTTPS server need some valid certificates which you may not have. If you need only a HTTP server is no reason to generate cerificates just to run the program. -Do not repeat the routes because the server will take always the last route to a target. +### Should I use http or https in the host address? +Yes, prefixes are mandatory to tell the server in which chain to put the route. Omitting that will skip the route. \ No newline at end of file diff --git a/conf.json b/conf.json index 8eacfe4..8600e2d 100644 --- a/conf.json +++ b/conf.json @@ -1,12 +1,11 @@ { - "ip":"", - "port":"8080", - "routes":{ - "/upload":"192.168.88.160:8080", - "#/files/":"192.168.88.160:8080", - "/Downloads/":"192.168.88.164:8000", - "/img":"192.168.88.161:8081", - "#":"the pattern / matches all paths not matched by other registered patterns", - "/":"192.168.88.161" - } -} \ No newline at end of file + "routes": { + "#": "the pattern / matches all paths not matched by other registered patterns", + "/": "http://192.168.88.250", + "/wrong": "192.168.88.250:8080", + "/upload": "http://192.168.88.250:8080", + "/hello": "https://192.168.88.250:8090", + "/static/": "http://192.168.88.250:8080", + "#/disabled": "192.168.88.250:8080" + } +} diff --git a/http.go b/http.go new file mode 100644 index 0000000..d501ec2 --- /dev/null +++ b/http.go @@ -0,0 +1,99 @@ +// simple http reverse proxy +// Copyright (C) 2017-2019 geosoft1 geosoft1@gmail.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +build http + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "net/http" + "net/http/httputil" + "net/url" + "os" + "path/filepath" + "strings" +) + +var configFile = flag.String("conf", "conf.json", "configuration file") +var httpAddress = flag.String("http", ":8080", "http address") +var verbose = flag.Bool("verbose", false, "explain what is being done") + +var config map[string]interface{} + +func NewReverseProxy(scheme, host string) *httputil.ReverseProxy { + return httputil.NewSingleHostReverseProxy(&url.URL{ + Scheme: scheme, + Host: host, + }) +} + +func Register(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if *verbose { + log.Printf("request %s%s", r.RemoteAddr, r.RequestURI) + } + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With") + p.ServeHTTP(w, r) + } +} + +func main() { + flag.Usage = func() { + fmt.Printf("usage: %s [options]\n", filepath.Base(os.Args[0])) + flag.PrintDefaults() + } + flag.Parse() + log.SetFlags(log.LstdFlags | log.Lshortfile) + + folder, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + log.Fatalln(err) + } + + file, err := os.Open(filepath.Join(folder, *configFile)) + if err != nil { + log.Fatalln(err) + } + + if err := json.NewDecoder(file).Decode(&config); err != nil { + log.Fatalln(err) + } + + for path, host := range config["routes"].(map[string]interface{}) { + log.Printf("%s -> %s", path, host) + if strings.HasPrefix(path, "#") { + // skip comments + continue + } + u, err := url.Parse(host.(string)) + if err != nil { + // skip invalid hosts + log.Println(err) + continue + } + http.HandleFunc(path, Register(NewReverseProxy(u.Scheme, u.Host))) + } + + log.Printf("start http server on %s", *httpAddress) + if err := http.ListenAndServe(*httpAddress, nil); err != nil { + log.Fatalln(err) + } +} diff --git a/https.go b/https.go new file mode 100644 index 0000000..70839d6 --- /dev/null +++ b/https.go @@ -0,0 +1,103 @@ +// simple https reverse proxy +// Copyright (C) 2017-2019 geosoft1 geosoft1@gmail.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +build https + +package main + +import ( + "crypto/tls" + "encoding/json" + "flag" + "fmt" + "log" + "net/http" + "net/http/httputil" + "net/url" + "os" + "path/filepath" + "strings" +) + +var configFile = flag.String("conf", "conf.json", "configuration file") +var httpsAddress = flag.String("https", ":8090", "https address") +var verbose = flag.Bool("verbose", false, "explain what is being done") + +var config map[string]interface{} + +func NewReverseProxy(scheme, host string) *httputil.ReverseProxy { + return httputil.NewSingleHostReverseProxy(&url.URL{ + Scheme: scheme, + Host: host, + }) +} + +func Register(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if *verbose { + log.Printf("request %s%s", r.RemoteAddr, r.RequestURI) + } + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With") + p.ServeHTTP(w, r) + } +} + +func main() { + flag.Usage = func() { + fmt.Printf("usage: %s [options]\n", filepath.Base(os.Args[0])) + flag.PrintDefaults() + } + flag.Parse() + log.SetFlags(log.LstdFlags | log.Lshortfile) + + folder, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + log.Fatalln(err) + } + + file, err := os.Open(filepath.Join(folder, *configFile)) + if err != nil { + log.Fatalln(err) + } + + if err := json.NewDecoder(file).Decode(&config); err != nil { + log.Fatalln(err) + } + + for path, host := range config["routes"].(map[string]interface{}) { + log.Printf("%s -> %s", path, host) + if strings.HasPrefix(path, "#") { + // skip comments + continue + } + u, err := url.Parse(host.(string)) + if err != nil { + // skip invalid hosts + log.Println(err) + continue + } + http.HandleFunc(path, Register(NewReverseProxy(u.Scheme, u.Host))) + } + + // allow you to use self signed certificates + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + // openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt + log.Printf("start https server on %s", *httpsAddress) + if err := http.ListenAndServeTLS(*httpsAddress, filepath.Join(folder, "server.crt"), filepath.Join(folder, "server.key"), nil); err != nil { + log.Fatalln(err) + } +} diff --git a/main.go b/main.go index 0d6cf38..31ed4ec 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,5 @@ -// simple http reverse proxy -// Copyright (C) 2017 geosoft1 geosoft1@gmail.com +// simple reverse proxy +// Copyright (C) 2017-2019 geosoft1 geosoft1@gmail.com // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -17,7 +17,9 @@ package main import ( + "crypto/tls" "encoding/json" + "flag" "fmt" "log" "net/http" @@ -25,57 +27,90 @@ import ( "net/url" "os" "path/filepath" + "strings" ) -func NewReverseProxy(target string) *httputil.ReverseProxy { +var configFile = flag.String("conf", "conf.json", "configuration file") +var httpAddress = flag.String("http", ":8080", "http address") +var httpsAddress = flag.String("https", ":8090", "https address") +var httpsEnabled = flag.Bool("https-enabled", false, "enable https server") +var verbose = flag.Bool("verbose", false, "explain what is being done") + +var config map[string]interface{} + +func NewReverseProxy(scheme, host string) *httputil.ReverseProxy { return httputil.NewSingleHostReverseProxy(&url.URL{ - Scheme: "http", - Host: target, + Scheme: scheme, + Host: host, }) } -func Handle(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) { +func Register(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - log.Println("request:", r.RemoteAddr, "want", r.RequestURI) - //Many webservers are configured to not serve pages if a request doesn’t appear from the same host. + if *verbose { + log.Printf("request %s%s", r.RemoteAddr, r.RequestURI) + } w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With") p.ServeHTTP(w, r) } } -var Config map[string]interface{} - func main() { - log.Print("init logger") - log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) + flag.Usage = func() { + fmt.Printf("usage: %s [options]\n", filepath.Base(os.Args[0])) + flag.PrintDefaults() + } + flag.Parse() + log.SetFlags(log.LstdFlags | log.Lshortfile) - pwd, err := filepath.Abs(filepath.Dir(os.Args[0])) + folder, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { - os.Exit(1) + log.Fatalln(err) } - log.Print("load configuration") - f, err := os.Open(filepath.ToSlash(pwd + "/conf.json")) + file, err := os.Open(filepath.Join(folder, *configFile)) if err != nil { - log.Println(err.Error()) - return + log.Fatalln(err) } - if err := json.NewDecoder(f).Decode(&Config); err != nil { - log.Println(err.Error()) - return + + if err := json.NewDecoder(file).Decode(&config); err != nil { + log.Fatalln(err) } - log.Print("init routes") - for Path, Target := range Config["routes"].(map[string]interface{}) { - // avoid add comments as route - if Path != "#" { - http.HandleFunc(Path, Handle(NewReverseProxy(Target.(string)))) - log.Printf("%s > %s", Path, Target) + for path, host := range config["routes"].(map[string]interface{}) { + log.Printf("%s -> %s", path, host) + if strings.HasPrefix(path, "#") { + // skip comments + continue } + u, err := url.Parse(host.(string)) + if err != nil { + // skip invalid hosts + log.Println(err) + continue + } + if u.Scheme == "https" && !*httpsEnabled { + log.Println("https scheme detected but server is not enabled, run with -https-enabled") + continue + } + http.HandleFunc(path, Register(NewReverseProxy(u.Scheme, u.Host))) } - Address := fmt.Sprintf("%s:%s", Config["ip"], Config["port"]) - log.Print("start listening on " + Address) - http.ListenAndServe(Address, nil) + if *httpsEnabled { + go func() { + // allow you to use self signed certificates + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + // openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt + log.Printf("start https server on %s", *httpsAddress) + if err := http.ListenAndServeTLS(*httpsAddress, filepath.Join(folder, "server.crt"), filepath.Join(folder, "server.key"), nil); err != nil { + log.Fatalln(err) + } + }() + } + + log.Printf("start http server on %s", *httpAddress) + if err := http.ListenAndServe(*httpAddress, nil); err != nil { + log.Fatalln(err) + } }