Skip to content

Instantly share code, notes, and snippets.

@rodkranz
Last active February 21, 2018 09:18
Show Gist options
  • Save rodkranz/0f80ae967652b17b681d91cd429dc6c4 to your computer and use it in GitHub Desktop.
Save rodkranz/0f80ae967652b17b681d91cd429dc6c4 to your computer and use it in GitHub Desktop.
Simple proxy to add CORS at header in endpoint API.
// 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