Git Product home page Git Product logo

go-reverse-proxy's Introduction

Go Reverse Proxy

A simple configuration-driven reverse proxy written in Go.

It has zero dependencies outside of the Go standard library.

Configuration

Define a slice of type Config, with the minimum set of fields being: Path and Upstream.

Configuration is defined in the routing/configuration file.

Upstreams are defined in the upstreams file.

Example Config

Below we explain the actual routing configuration committed into this repo...

Proxy Request

Config{
  Path:     "/anything/standard",
  Upstream: upstreams.HTTPBin,
}

Requests

  • /anything/standard

Result

The request will be proxied straight through to the specified upstream without any modifications.


Proxy Request using Regular Expression

Config{
  Path:     "/anything/(?:foo|bar)$",
  Upstream: upstreams.HTTPBin,
}

Requests

  • /anything/foo
  • /anything/bar

Result

Both requests will be proxied straight through to the specified upstream without any modifications.


Proxy Request with Modified Path

Config{
  Path:       `/(?P<cap>foo\w{3})`,
  Upstream:   upstreams.HTTPBin,
  ModifyPath: "/anything/${cap}",
}

Requests

  • /fooabc
  • /fooxyz

Result

Both requests will be proxied through to the specified upstream but the path will be modified to include the captured information: /anything/abc and /anything/xyz.


Override with Modified Path

Config{
  Path:     "/(?P<start>anything)/(?P<cap>foobar)$",
  Upstream: upstreams.HTTPBin,
  Override: Override{
    Header:     "X-BF-Testing",
    Match:      "integralist",
    ModifyPath: "/anything/newthing${cap}",
  },
}

Requests

  • /anything/foobar
  • /anything/foobar (+ HTTP Request Header X-BF-Testing: integralist)

Result

The request will be proxied straight through to the specified upstream without any modifications.

If the relevant request header is specified, then the request will be proxied through to the specified upstream but the path will be modified to include the captured information: /anything/newthingfoobar.


Modified Path + Override with Modified Path

Config{
  Path:       "/(?P<cap>double-checks)$",
  Upstream:   upstreams.HTTPBin,
  ModifyPath: "/anything/toplevel-modified-${cap}",
  Override: Override{
    Header:     "X-BF-Testing",
    Match:      "integralist",
    ModifyPath: "/anything/override-modified-${cap}",
  },
}

Requests

  • /double-checks
  • /double-checks (+ HTTP Request Header X-BF-Testing: integralist)

Result

The request will be proxied through to the specified upstream but the path will be modified to include the captured information: /anything/toplevel-modified-double-checks.

If the relevant request header is specified, then the request will be proxied through to the specified upstream but the path will be modified to include the captured information: /anything/override-modified-double-checks.


Override to Different Upstream

Config{
  Path:     "/anything/(?P<cap>integralist)",
  Upstream: upstreams.HTTPBin,
  Override: Override{
    Header:     "X-BF-Testing",
    Match:      "integralist",
    ModifyPath: "/about",
    Upstream:   upstreams.Integralist,
  },
}

Requests

  • /anything/integralist
  • /anything/integralist (+ HTTP Request Header X-BF-Testing: integralist)

Result

The request will be proxied straight through to the specified upstream without any modifications.

If the relevant request header is specified, then the request will be proxied through to a different specified upstream and the path will also be modified.

Note: although we use a named capture group, we don't actually utilise it anywhere in the rest of the configuration, so it's effectively a no-op.


Query String Override

Config{
  Path:     "/about",
  Upstream: upstreams.HTTPBin,
  Override: Override{
    Query:    "s",
    Match:    "integralist",
    Upstream: upstreams.Integralist,
  },
}

Requests

  • /about
  • /about?s=integralist

Result

The request will be proxied straight through to the specified upstream without any modifications.

If the relevant query parameter is specified, then the request will be proxied through to a different specified upstream.


Query String Override with Regular Expression

Config{
  Path:     "/anything/querytest",
  Upstream: upstreams.HTTPBin,
  Override: Override{
    Query:      "s",
    Match:      `integralist(?P<cap>\d{1,3})$`,
    MatchType:  "regex",
    ModifyPath: "/anything/newthing${cap}",
  },
}

Requests

  • /anything/querytest
  • /anything/querytest?s=integralist123
  • /anything/querytest?s=integralist456

Result

The first request will be proxied straight through to the specified upstream without any modifications.

If the relevant query parameter is specified, then the second and third requests will have their path modified to include the captured information: /anything/newthing123 and /anything/newthing456.

Response Headers

We set the following response headers (not all will be set depending on the configuration):

X-Forwarded-Host
X-Origin-Host
X-Router-Upstream
X-Router-Upstream-OriginalHost
X-Router-Upstream-OriginalPath
X-Router-Upstream-OriginalPathModified
X-Router-Upstream-Override
X-Router-Upstream-OverrideHost
X-Router-Upstream-OverridePath

Usage

make run

Note: the application listens on port 9001.

curl -v http://localhost:9001/some/path/you/configured

Tests

make test

Load Test

We use vegeta for load testing, so make sure you have that installed.

make stress

Example output:

Requests      [total, rate]            1500, 50.03
Duration      [total, attack, wait]    30.11237994s, 29.982166788s, 130.213152ms
Latencies     [mean, 50, 95, 99, max]  154.522948ms, 96.76258ms, 358.770472ms, 1.076826656s, 2.954136535s
Bytes In      [total, mean]            2039772, 1359.85
Bytes Out     [total, mean]            0, 0.00
Success       [ratio]                  100.00%
Status Codes  [code:count]             200:1500
Error Set:

TODO

  • Look at implementing thread pool processing on a host or server basis.
  • Verify if DNS caching (or request memoization) would affect latency results?
  • Review 301 redirect behaviour to be sure we don't need to handle that differently.
  • Flesh out some unit tests (not just integration testing)

go-reverse-proxy's People

Contributors

integralist avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

alexjoedt

go-reverse-proxy's Issues

i am not able add one of my own upstream

Hi Mark,

Many thanks for your wonderful go proxy. I am very very new noobie. Please forgive my silly questions.

i am not able add one of my own upstream

Upstreams
// HTTPBin is used for testing
var Alice = &Upstream{
Name: "alice",
Host: "example.com",
}

configurations
Config{
Path: "/contact-us",
Upstream: upstreams.Alice,
}

route
/contact-us

result
$ http http://127.0.0.1:9001/contact-us`
HTTP/1.1 404 Not Found
Content-Length: 19
Content-Type: text/plain; charset=utf-8
Date: Sat, 06 Feb 2021 17:34:20 GMT
X-Content-Type-Options: nosniff
404 page not found

Consider different pattern replacement syntax

Daniel Katz (@katzdm, colleague at BuzzFeed) suggested...

It might be that the regex <cap>-capturing syntax is something standard that I haven't come across before, but I found it a bit awkward to read. An implementation of a similar matching system which I personally like is implemented in Google's Copybara library for moving source code between repositories (http://github.com/google/copybara). In particular, I'm a fan of their regex syntax used in the core.replace transformation.

core.replace(
       before = "https://some_internal/url/${pkg}.html",
       after = "https://example.com/${pkg}.html",
       regex_groups = {
           "pkg": ".*",
       },
   )

The regex_groups syntax has the advantage of allowing for the definition of arbitrarily many capture-groups, and uses a ubiquitous syntax (i.e. ${var-name}) in the before/after fields, arguably making the before/after patterns a bit easier on the eyes.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.