├── .gitignore ├── LICENSE ├── README.md └── nanoproxy.go /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 David Foster 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nanoproxy 2 | 3 | This is a tiny HTTP forward proxy written in [Go], 4 | for me to gain experience in the Go language. 5 | 6 | This proxy accepts all requests and forwards them directly to the 7 | origin server. It performs no caching. 8 | 9 | Despite this not being a full proxy implementation, it is blazing fast. 10 | In particular it is significantly faster than Squid and slightly faster than 11 | Apache's mod_proxy. This demonstrates that Go's built-in HTTP library is 12 | of a very high quality and that the Go runtime is quite performant. 13 | 14 | Only `xkcd.com` has been really tested with this proxy. 15 | Many other sites don't work with the current implementation. 16 | 17 | ## Prerequisites 18 | 19 | * Go 1.3.3, or a compatible version 20 | 21 | ## Installation 22 | 23 | * Clone this repository. 24 | 25 | ``` 26 | git clone git@github.com:davidfstr/nanoproxy.git 27 | cd nanoproxy 28 | ``` 29 | 30 | * Configure your web browser to route all HTTP traffic through `localhost:8080`. 31 | 32 | ## Usage 33 | 34 | * Start the proxy: `go run nanoproxy.go` 35 | 36 | * Open your web browser to `http://xkcd.com` or some other page on that site. 37 | 38 | [Go]: https://golang.org 39 | 40 | ## Notes 41 | 42 | * Go's HTTP server implementation is really good. I read it all. 43 | Only missing feature I desire is the ability to process multiple 44 | pipelined HTTP requests in parallel. 45 | * Go's HTTP client implementation is easy to use, based on my limited 46 | experience in this proxy. I have not read its implementation. -------------------------------------------------------------------------------- /nanoproxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | var verbose = false; 11 | 12 | var passthruRequestHeaderKeys = [...]string{ 13 | "Accept", 14 | "Accept-Encoding", 15 | "Accept-Language", 16 | "Cache-Control", 17 | "Cookie", 18 | "Referer", 19 | "User-Agent", 20 | } 21 | 22 | var passthruResponseHeaderKeys = [...]string{ 23 | "Content-Encoding", 24 | "Content-Language", 25 | "Content-Type", 26 | "Cache-Control", // TODO: Is this valid in a response? 27 | "Date", 28 | "Etag", 29 | "Expires", 30 | "Last-Modified", 31 | "Location", 32 | "Server", 33 | "Vary", 34 | } 35 | 36 | func main() { 37 | handler := http.DefaultServeMux 38 | 39 | handler.HandleFunc("/", handleFunc) 40 | 41 | s := &http.Server{ 42 | Addr: ":8080", 43 | Handler: handler, 44 | ReadTimeout: 10 * time.Second, 45 | WriteTimeout: 10 * time.Second, 46 | MaxHeaderBytes: 1 << 20, 47 | } 48 | 49 | s.ListenAndServe() 50 | } 51 | 52 | func handleFunc(w http.ResponseWriter, r *http.Request) { 53 | fmt.Printf("--> %v %v\n", r.Method, r.URL) 54 | 55 | // Construct filtered header to send to origin server 56 | hh := http.Header{} 57 | for _, hk := range passthruRequestHeaderKeys { 58 | if hv, ok := r.Header[hk]; ok { 59 | hh[hk] = hv 60 | } 61 | } 62 | 63 | // Construct request to send to origin server 64 | rr := http.Request{ 65 | Method: r.Method, 66 | URL: r.URL, 67 | Header: hh, 68 | Body: r.Body, 69 | // TODO: Is this correct for a 0 value? 70 | // Perhaps a 0 may need to be reinterpreted as -1? 71 | ContentLength: r.ContentLength, 72 | Close: r.Close, 73 | } 74 | 75 | // Forward request to origin server 76 | resp, err := http.DefaultTransport.RoundTrip(&rr) 77 | if err != nil { 78 | // TODO: Passthru more error information 79 | http.Error(w, "Could not reach origin server", 500) 80 | return 81 | } 82 | defer resp.Body.Close() 83 | 84 | if (verbose) { 85 | fmt.Printf("<-- %v %+v\n", resp.Status, resp.Header) 86 | } else { 87 | fmt.Printf("<-- %v\n", resp.Status) 88 | } 89 | 90 | // Transfer filtered header from origin server -> client 91 | respH := w.Header() 92 | for _, hk := range passthruResponseHeaderKeys { 93 | if hv, ok := resp.Header[hk]; ok { 94 | respH[hk] = hv 95 | } 96 | } 97 | w.WriteHeader(resp.StatusCode) 98 | 99 | // Transfer response from origin server -> client 100 | if resp.ContentLength > 0 { 101 | // (Ignore I/O errors, since there's nothing we can do) 102 | io.CopyN(w, resp.Body, resp.ContentLength) 103 | } else if (resp.Close) { // TODO: Is this condition right? 104 | // Copy until EOF or some other error occurs 105 | for { 106 | if _, err := io.Copy(w, resp.Body); err != nil { 107 | break 108 | } 109 | } 110 | } 111 | } --------------------------------------------------------------------------------