Last active
February 21, 2018 09:18
-
-
Save rodkranz/0f80ae967652b17b681d91cd429dc6c4 to your computer and use it in GitHub Desktop.
Simple proxy to add CORS at header in endpoint API.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2016 Kranz. All rights reserved. | |
// Use of this source code is governed by a MIT-style | |
// license that can be found in the LICENSE file. | |
// | |
// Execute: | |
// | |
// $ ./go run main.go -target http://www.google.com | |
// | |
package main | |
import ( | |
"net/http" | |
"fmt" | |
"os" | |
"log" | |
"time" | |
"net/url" | |
"strings" | |
"io" | |
"net" | |
"flag" | |
) | |
// Local Address and HttpTarget | |
var ( | |
addr, httpTarget string | |
timeout int | |
) | |
// Server Object with configuration of server. | |
type Server struct { | |
*http.ServeMux | |
bridgeTo string | |
fetch *http.Client | |
//RequestOptions func(http.ResponseWriter) | |
Middleware func(http.ResponseWriter, *http.Request, http.HandlerFunc) | |
} | |
// NewServerProxy return new server proxy with default configuration. | |
func NewServerProxy() *Server { | |
timeout := time.Duration(timeout) * time.Second | |
// Timeout is to security that client never will block the application for eternity. | |
fetch := &http.Client{ | |
Transport: &http.Transport{ | |
Dial: (&net.Dialer{ | |
Timeout: timeout, | |
}).Dial, | |
TLSHandshakeTimeout: timeout, | |
}, | |
Timeout: timeout, | |
} | |
return &Server{ | |
ServeMux: http.NewServeMux(), | |
fetch: fetch, | |
} | |
} | |
// BridgeTo define target address to create a bridge to there. | |
func (s *Server) BridgeTo(address string) { | |
s.bridgeTo = address | |
} | |
// ServeHTTP wrapper for ServerHttp and add proxy handler for any request. | |
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
timeStart := time.Now() | |
if s.Middleware != nil { | |
s.Middleware(w, r, s.handlerProxy) | |
} else { | |
s.handlerProxy(w, r) | |
} | |
timeEnd := time.Now().Sub(timeStart) | |
log.Printf("Server execution time was %s.\n", timeEnd.String()) | |
} | |
// getParsedUrl parse url to keep it clean. | |
func (s *Server) getParsedUrl(r *http.Request) (string, error) { | |
urlRequest, err := url.Parse(s.bridgeTo + r.RequestURI) | |
if err != nil { | |
return "", err | |
} | |
u := fmt.Sprintf("%s://%s%s", | |
urlRequest.Scheme, | |
urlRequest.Host, | |
strings.Replace(urlRequest.Path, "//", "/", -1), | |
) | |
return u, nil | |
} | |
// handlerProxy Do a request to final host with URI local and added cors. | |
func (s *Server) handlerProxy(w http.ResponseWriter, r *http.Request) { | |
u, err := s.getParsedUrl(r) | |
if err != nil { | |
fmt.Fprintf(w, "getParsedUrl error: %s", err.Error()) | |
return | |
} | |
req, err := http.NewRequest(r.Method, u, r.Body) | |
if err != nil { | |
fmt.Fprintf(w, "NewRequest error: %s", err.Error()) | |
return | |
} | |
// reply header from request to target. | |
req.Header = r.Header | |
// execute request with origin information. | |
response, err := s.fetch.Do(req) | |
if err != nil { | |
fmt.Fprintf(w, "Do request error: %s", err.Error()) | |
return | |
} | |
// Replay header from target | |
for k, v := range response.Header { | |
for _, h := range v { | |
w.Header().Add(k, h) | |
} | |
} | |
// write status code from target. | |
w.WriteHeader(response.StatusCode) | |
// close response body | |
defer response.Body.Close() | |
// copy any body data from target to origin. | |
if _, err := io.Copy(w, response.Body); err != nil { | |
log.Fatal(err) | |
} | |
} | |
// Start initialise server proxy. | |
func (s *Server) Start(addr string) error { | |
log.Printf("Server listing %s\n", addr) | |
if err := http.ListenAndServe(addr, s); err != nil { | |
return fmt.Errorf("server Error: %v", err) | |
} | |
return nil | |
} | |
// middleware cors add headers to allow clients request this endpoint without blockers. | |
func middleware(rw http.ResponseWriter, r *http.Request, handlerFunc http.HandlerFunc) { | |
rw.Header().Add("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With") | |
rw.Header().Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") | |
rw.Header().Add("Access-Control-Allow-Origin", "*") | |
rw.Header().Add("Access-Control-Max-Age", "1000") | |
if r.Method == http.MethodOptions { | |
rw.WriteHeader(http.StatusOK) | |
return | |
} | |
handlerFunc(rw, r) | |
} | |
func init() { | |
flag.IntVar(&timeout, "timeout", 30, "limit time for proxy in seconds") | |
flag.StringVar(&addr, "addr", ":4000", "listen local address") | |
flag.StringVar(&httpTarget, "target", "", "target address") | |
flag.Parse() | |
} | |
// Main execution | |
func main() { | |
if httpTarget == "" { | |
log.Fatal("The target Address is required") | |
os.Exit(1) | |
} | |
// start new instance of proxy | |
myServer := NewServerProxy() | |
// defined the target endpoint | |
myServer.BridgeTo(httpTarget) | |
// Middleware function response for added cors and execute proxy if it's necessary | |
myServer.Middleware = middleware | |
// start application | |
if err := myServer.Start(addr); err != nil { | |
log.Fatal(err) | |
} | |
os.Exit(0) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment