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)
+ }
}