Skip to content

Commit

Permalink
Merge pull request #2 from go-httpproxy/develop
Browse files Browse the repository at this point in the history
v1.1
  • Loading branch information
orkunkaraduman authored Feb 4, 2018
2 parents 40fe09b + 9455f5e commit ce8bc1f
Show file tree
Hide file tree
Showing 8 changed files with 433 additions and 95 deletions.
187 changes: 186 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,186 @@
# go-httpproxy
# A Go HTTP proxy library which has KISS principle

## Introduction

`github.com/go-httpproxy/httpproxy` repository provides an HTTP proxy library
for Go (golang).

The library is regular HTTP proxy; supports HTTP, HTTPS through CONNECT. And
also provides HTTPS connection using "Man in the Middle" style attack.

It's easy to use. `httpproxy.Proxy` implements `Handler` interface of `net/http`
package to offer `http.ListenAndServe` function.

### Keep it simple, stupid!

> KISS is an acronym for "Keep it simple, stupid" as a design principle. The
KISS principle states that most systems work best if they are kept simple rather
than made complicated; therefore simplicity should be a key goal in design and
unnecessary complexity should be avoided. [Wikipedia]

## Usage

Library has two significant structs: Proxy and Context.

### Proxy struct

```go
// Proxy defines parameters for running an HTTP Proxy. It implements
// http.Handler interface for ListenAndServe function. If you need, you must
// fill Proxy struct before handling requests.
type Proxy struct {
// Session number of last proxy request.
SessionNo int64

// RoundTripper interface to obtain remote response.
// By default, it uses &http.Transport{}.
Rt http.RoundTripper

// Certificate key pair.
Ca tls.Certificate

// User data to use free.
UserData interface{}

// Error handler.
OnError func(ctx *Context, when string, err *Error, opErr error)

// Accept handler. It greets proxy request like ServeHTTP function of
// http.Handler.
// If it returns true, stops processing proxy request.
OnAccept func(ctx *Context, w http.ResponseWriter, r *http.Request) bool

// Auth handler. If you need authentication, set this handler.
// If it returns true, authentication succeeded.
OnAuth func(ctx *Context, user string, pass string) bool

// Connect handler. It sets connect action and new host.
// If len(newhost) > 0, host changes.
OnConnect func(ctx *Context, host string) (ConnectAction ConnectAction,
newHost string)

// Request handler. It greets remote request.
// If it returns non-nil response, stops processing remote request.
OnRequest func(ctx *Context, req *http.Request) (resp *http.Response)

// Response handler. It greets remote response.
// Remote response sends after this handler.
OnResponse func(ctx *Context, req *http.Request, resp *http.Response)

// If ConnectAction is ConnectMitm, it sets chunked to Transfer-Encoding.
// By default, it is true.
MitmChunked bool
}
```

### Context struct

```go
// Context keeps context of each proxy request.
type Context struct {
// Pointer of Proxy struct handled this context.
// It's using internally. Don't change in Context struct!
Prx *Proxy

// Session number of this context obtained from Proxy struct.
SessionNo int64

// Sub session number of processing remote connection.
SubSessionNo int64

// Action of after the CONNECT, if proxy request method is CONNECT.
// It's using internally. Don't change in Context struct!
ConnectAction ConnectAction

// Proxy request, if proxy request method is CONNECT.
// It's using internally. Don't change in Context struct!
ConnectReq *http.Request

// Remote host, if proxy request method is CONNECT.
// It's using internally. Don't change in Context struct!
ConnectHost string

// User data to use free.
UserData interface{}
}
```

### Demo code

```go
package main

import (
"log"
"net/http"

"github.com/go-httpproxy/httpproxy"
)

func OnError(ctx *httpproxy.Context, when string,
err *httpproxy.Error, opErr error) {
// Log errors.
log.Printf("ERR: %s: %s [%s]", when, err, opErr)
}

func OnAccept(ctx *httpproxy.Context, w http.ResponseWriter,
r *http.Request) bool {
// Handle local request has path "/info"
if r.Method == "GET" && !r.URL.IsAbs() && r.URL.Path == "/info" {
w.Write([]byte("This is go-httpproxy."))
return true
}
return false
}

func OnAuth(ctx *httpproxy.Context, user string, pass string) bool {
// Auth test user.
if user == "test" && pass == "1234" {
return true
}
return false
}

func OnConnect(ctx *httpproxy.Context, host string) (
ConnectAction httpproxy.ConnectAction, newHost string) {
// Apply "Man in the Middle" to all ssl connections. Never change host.
return httpproxy.ConnectMitm, host
}

func OnRequest(ctx *httpproxy.Context, req *http.Request) (
resp *http.Response) {
// Log proxying requests.
log.Printf("INFO: Proxy: %s %s", req.Method, req.URL.String())
return
}

func OnResponse(ctx *httpproxy.Context, req *http.Request,
resp *http.Response) {
// Add header "Via: go-httpproxy".
resp.Header.Add("Via", "go-httpproxy")
}

func main() {
// Create a new proxy with default certificate pair.
prx, _ := httpproxy.NewProxy()

// Set handlers.
prx.OnError = OnError
prx.OnAccept = OnAccept
prx.OnAuth = OnAuth
prx.OnConnect = OnConnect
prx.OnRequest = OnRequest
prx.OnResponse = OnResponse

// Listen...
http.ListenAndServe(":8080", prx)
}
```

## GoDoc

[https://godoc.org/github.com/go-httpproxy/httpproxy](https://godoc.org/github.com/go-httpproxy/httpproxy)

## To-Do

* GoDoc
2 changes: 2 additions & 0 deletions ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"
)

// Default certificate.
var DefaultCaCert = []byte(`-----BEGIN CERTIFICATE-----
MIIFkzCCA3ugAwIBAgIJAKEbW2ujNjX9MA0GCSqGSIb3DQEBCwUAMGAxCzAJBgNV
BAYTAlRSMREwDwYDVQQIDAhJc3RhbmJ1bDEVMBMGA1UECgwMZ28taHR0cHByb3h5
Expand Down Expand Up @@ -46,6 +47,7 @@ Ii9Vb07WDMQXou0ZZs7rnjAKo+sfFElTFewtS1wif4ZYBUJN1ln9G8qKaxbAiElm
MgzNfZ7WlnaJf2rfHJbvK9VqJ9z6dLRYPjCHhakJBtzsMdxysEGJ
-----END CERTIFICATE-----`)

// Default key.
var DefaultCaKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEA18cwaaZzhdDEpUXpR9pkYRqsSdT30WhynFhFtcaBOf4eYdpt
AJWL2ipo3Ac6bh+YgWfywG4prrSfWOJl+dQ59w439vLek/waBcEeFx+wJ6PFu0ur
Expand Down
38 changes: 32 additions & 6 deletions context.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
package httpproxy

import "net/http"
import (
"bufio"
"crypto/tls"
"net/http"
)

// Context defines context of each proxy connection.
// Context keeps context of each proxy request.
type Context struct {
Prx *Proxy
SessionNo int64
// Pointer of Proxy struct handled this context.
// It's using internally. Don't change in Context struct!
Prx *Proxy

// Session number of this context obtained from Proxy struct.
SessionNo int64

// Sub session number of processing remote connection.
SubSessionNo int64

// Action of after the CONNECT, if proxy request method is CONNECT.
// It's using internally. Don't change in Context struct!
ConnectAction ConnectAction
ConnectReq *http.Request
UserData interface{}

// Proxy request, if proxy request method is CONNECT.
// It's using internally. Don't change in Context struct!
ConnectReq *http.Request

// Remote host, if proxy request method is CONNECT.
// It's using internally. Don't change in Context struct!
ConnectHost string

// User data to use free.
UserData interface{}

hijTLSConn *tls.Conn
hijTLSReader *bufio.Reader
}
38 changes: 29 additions & 9 deletions demo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,61 @@ import (
"github.com/go-httpproxy/httpproxy"
)

func OnError(ctx *httpproxy.Context, when string, err *httpproxy.Error, opErr error) {
log.Printf("%s %s %s", when, err, opErr)
func OnError(ctx *httpproxy.Context, when string,
err *httpproxy.Error, opErr error) {
// Log errors.
log.Printf("ERR: %s: %s [%s]", when, err, opErr)
}

func OnAccept(ctx *httpproxy.Context, req *http.Request) *http.Response {
return nil
func OnAccept(ctx *httpproxy.Context, w http.ResponseWriter,
r *http.Request) bool {
// Handle local request has path "/info"
if r.Method == "GET" && !r.URL.IsAbs() && r.URL.Path == "/info" {
w.Write([]byte("This is go-httpproxy."))
return true
}
return false
}

func OnAuth(ctx *httpproxy.Context, user string, pass string) bool {
// Auth test user.
if user == "test" && pass == "1234" {
return true
}
return false
}

func OnConnect(ctx *httpproxy.Context, host string) (httpproxy.ConnectAction, string) {
func OnConnect(ctx *httpproxy.Context, host string) (
ConnectAction httpproxy.ConnectAction, newHost string) {
// Apply "Man in the Middle" to all ssl connections. Never change host.
return httpproxy.ConnectMitm, host
}

func OnRequest(ctx *httpproxy.Context, req *http.Request) *http.Response {
return nil
func OnRequest(ctx *httpproxy.Context, req *http.Request) (
resp *http.Response) {
// Log proxying requests.
log.Printf("INFO: Proxy: %s %s", req.Method, req.URL.String())
return
}

func OnResponse(ctx *httpproxy.Context, req *http.Request, resp *http.Response) {
resp.Header.Add("Via", "test")
func OnResponse(ctx *httpproxy.Context, req *http.Request,
resp *http.Response) {
// Add header "Via: go-httpproxy".
resp.Header.Add("Via", "go-httpproxy")
}

func main() {
// Create a new proxy with default certificate pair.
prx, _ := httpproxy.NewProxy()

// Set handlers.
prx.OnError = OnError
prx.OnAccept = OnAccept
prx.OnAuth = OnAuth
prx.OnConnect = OnConnect
prx.OnRequest = OnRequest
prx.OnResponse = OnResponse

// Listen...
http.ListenAndServe(":8080", prx)
}
Loading

0 comments on commit ce8bc1f

Please sign in to comment.