Skip to content

Commit

Permalink
Merge pull request #30 from nextrevision/proxy-responses
Browse files Browse the repository at this point in the history
Add boolean flag to allow proxying responses back to client
  • Loading branch information
darklynx committed May 11, 2018
2 parents 5abadb7 + a90d135 commit 5125ec8
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 39 deletions.
19 changes: 10 additions & 9 deletions baskets.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"io"
"io/ioutil"
"log"
"net/http"
Expand All @@ -17,10 +16,11 @@ const DoNotForwardHeader = "X-Do-Not-Forward"

// BasketConfig describes single basket configuration.
type BasketConfig struct {
ForwardURL string `json:"forward_url"`
InsecureTLS bool `json:"insecure_tls"`
ExpandPath bool `json:"expand_path"`
Capacity int `json:"capacity"`
ForwardURL string `json:"forward_url"`
ProxyResponse bool `json:"proxy_response"`
InsecureTLS bool `json:"insecure_tls"`
ExpandPath bool `json:"expand_path"`
Capacity int `json:"capacity"`
}

// ResponseConfig describes response that is generates by service upon HTTP request sent to a basket.
Expand Down Expand Up @@ -126,7 +126,7 @@ func ToRequestData(req *http.Request) *RequestData {
}

// Forward forwards request data to specified URL
func (req *RequestData) Forward(client *http.Client, config BasketConfig, basket string) {
func (req *RequestData) Forward(client *http.Client, config BasketConfig, basket string) *http.Response {
body := strings.NewReader(req.Body)
forwardURL, err := url.ParseRequestURI(config.ForwardURL)

Expand Down Expand Up @@ -165,12 +165,13 @@ func (req *RequestData) Forward(client *http.Client, config BasketConfig, basket

if err != nil {
log.Printf("[error] failed to forward request: %s", err)
} else {
io.Copy(ioutil.Discard, response.Body)
response.Body.Close()
return &http.Response{}
}

return response
}
}
return &http.Response{}
}

func expand(url string, original string, basket string) string {
Expand Down
6 changes: 6 additions & 0 deletions baskets_bolt.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const DbTypeBolt = "bolt"
const (
boltOptExpandPath = 1 << iota
boltOptInsecureTLS
boltOptProxyResponse
)

var (
Expand Down Expand Up @@ -49,6 +50,9 @@ func toOpts(config BasketConfig) []byte {
if config.InsecureTLS {
opts |= boltOptInsecureTLS
}
if config.ProxyResponse {
opts |= boltOptProxyResponse
}

return []byte{opts}
}
Expand All @@ -57,9 +61,11 @@ func fromOpts(opts []byte, config *BasketConfig) {
if len(opts) > 0 {
config.ExpandPath = opts[0]&boltOptExpandPath != 0
config.InsecureTLS = opts[0]&boltOptInsecureTLS != 0
config.ProxyResponse = opts[0]&boltOptProxyResponse != 0
} else {
config.ExpandPath = false
config.InsecureTLS = false
config.ProxyResponse = false
}
}

Expand Down
23 changes: 17 additions & 6 deletions baskets_bolt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import (
)

func TestToOpts(t *testing.T) {
assert.Equal(t, []byte{0}, toOpts(BasketConfig{ExpandPath: false, InsecureTLS: false}), "wrong options")
assert.Equal(t, []byte{1}, toOpts(BasketConfig{ExpandPath: true, InsecureTLS: false}), "wrong options")
assert.Equal(t, []byte{2}, toOpts(BasketConfig{ExpandPath: false, InsecureTLS: true}), "wrong options")
assert.Equal(t, []byte{3}, toOpts(BasketConfig{ExpandPath: true, InsecureTLS: true}), "wrong options")
assert.Equal(t, []byte{0}, toOpts(BasketConfig{ExpandPath: false, InsecureTLS: false, ProxyResponse: false}), "wrong options")
assert.Equal(t, []byte{1}, toOpts(BasketConfig{ExpandPath: true, InsecureTLS: false, ProxyResponse: false}), "wrong options")
assert.Equal(t, []byte{2}, toOpts(BasketConfig{ExpandPath: false, InsecureTLS: true, ProxyResponse: false}), "wrong options")
assert.Equal(t, []byte{4}, toOpts(BasketConfig{ExpandPath: false, InsecureTLS: false, ProxyResponse: true}), "wrong options")
assert.Equal(t, []byte{7}, toOpts(BasketConfig{ExpandPath: true, InsecureTLS: true, ProxyResponse: true}), "wrong options")
}

func TestFromOpts(t *testing.T) {
Expand All @@ -23,21 +24,31 @@ func TestFromOpts(t *testing.T) {
fromOpts([]byte{}, &config)
assert.False(t, config.ExpandPath, "wrong 'ExpandPath' value")
assert.False(t, config.InsecureTLS, "wrong 'InsecureTLS' value")
assert.False(t, config.ProxyResponse, "wrong 'ProxyResponse' value")

// reset
fromOpts([]byte{0}, &config)
assert.False(t, config.ExpandPath, "wrong 'ExpandPath' value")
assert.False(t, config.InsecureTLS, "wrong 'InsecureTLS' value")
assert.False(t, config.ProxyResponse, "wrong 'ProxyResponse' value")

// toOpts => fromOpts
fromOpts(toOpts(BasketConfig{ExpandPath: true, InsecureTLS: false}), &config)
fromOpts(toOpts(BasketConfig{ExpandPath: true, InsecureTLS: false, ProxyResponse: false}), &config)
assert.True(t, config.ExpandPath, "wrong 'ExpandPath' value")
assert.False(t, config.InsecureTLS, "wrong 'InsecureTLS' value")
assert.False(t, config.ProxyResponse, "wrong 'ProxyResponse' value")

// toOpts => fromOpts
fromOpts(toOpts(BasketConfig{ExpandPath: false, InsecureTLS: true}), &config)
fromOpts(toOpts(BasketConfig{ExpandPath: false, InsecureTLS: true, ProxyResponse: false}), &config)
assert.False(t, config.ExpandPath, "wrong 'ExpandPath' value")
assert.True(t, config.InsecureTLS, "wrong 'InsecureTLS' value")
assert.False(t, config.ProxyResponse, "wrong 'ProxyResponse' value")

// toOpts => fromOpts
fromOpts(toOpts(BasketConfig{ExpandPath: false, InsecureTLS: false, ProxyResponse: true}), &config)
assert.False(t, config.ExpandPath, "wrong 'ExpandPath' value")
assert.False(t, config.InsecureTLS, "wrong 'InsecureTLS' value")
assert.True(t, config.ProxyResponse, "wrong 'ProxyResponse' value")
}

func TestBoltDatabase_Create(t *testing.T) {
Expand Down
15 changes: 8 additions & 7 deletions baskets_sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var sqlSchema = []string{
token varchar(100) NOT NULL,
capacity integer NOT NULL,
forward_url text NOT NULL,
proxy_response boolean NOT NULL,
insecure_tls boolean NOT NULL,
expand_path boolean NOT NULL,
requests_count integer NOT NULL DEFAULT 0,
Expand Down Expand Up @@ -90,8 +91,8 @@ func (basket *sqlBasket) Config() BasketConfig {
config := BasketConfig{}

err := basket.db.QueryRow(
unifySQL(basket.dbType, "SELECT capacity, forward_url, insecure_tls, expand_path FROM rb_baskets WHERE basket_name = $1"),
basket.name).Scan(&config.Capacity, &config.ForwardURL, &config.InsecureTLS, &config.ExpandPath)
unifySQL(basket.dbType, "SELECT capacity, forward_url, proxy_response, insecure_tls, expand_path FROM rb_baskets WHERE basket_name = $1"),
basket.name).Scan(&config.Capacity, &config.ForwardURL, &config.ProxyResponse, &config.InsecureTLS, &config.ExpandPath)
if err != nil {
log.Printf("[error] failed to get basket config: %s - %s", basket.name, err)
}
Expand All @@ -101,8 +102,8 @@ func (basket *sqlBasket) Config() BasketConfig {

func (basket *sqlBasket) Update(config BasketConfig) {
_, err := basket.db.Exec(
unifySQL(basket.dbType, "UPDATE rb_baskets SET capacity = $1, forward_url = $2, insecure_tls = $3, expand_path = $4 WHERE basket_name = $5"),
config.Capacity, config.ForwardURL, config.InsecureTLS, config.ExpandPath, basket.name)
unifySQL(basket.dbType, "UPDATE rb_baskets SET capacity = $1, forward_url = $2, proxy_response = $3, insecure_tls = $4, expand_path = $5 WHERE basket_name = $6"),
config.Capacity, config.ForwardURL, config.ProxyResponse, config.InsecureTLS, config.ExpandPath, basket.name)
if err != nil {
log.Printf("[error] failed to update basket config: %s - %s", basket.name, err)
} else {
Expand Down Expand Up @@ -287,8 +288,8 @@ func (sdb *sqlDatabase) Create(name string, config BasketConfig) (BasketAuth, er
}

basket, err := sdb.db.Exec(
unifySQL(sdb.dbType, "INSERT INTO rb_baskets (basket_name, token, capacity, forward_url, insecure_tls, expand_path) VALUES($1, $2, $3, $4, $5, $6)"),
name, token, config.Capacity, config.ForwardURL, config.InsecureTLS, config.ExpandPath)
unifySQL(sdb.dbType, "INSERT INTO rb_baskets (basket_name, token, capacity, forward_url, proxy_response, insecure_tls, expand_path) VALUES($1, $2, $3, $4, $5, $6, $7)"),
name, token, config.Capacity, config.ForwardURL, config.ProxyResponse, config.InsecureTLS, config.ExpandPath)
if err != nil {
return auth, fmt.Errorf("Failed to create basket: %s - %s", name, err)
}
Expand Down Expand Up @@ -422,7 +423,7 @@ func unifySQL(dbType string, sql string) string {
case "mysql", "sqlite3":
// replace $n with ?
return pgParams.ReplaceAllString(sql, "?")
// case "postgres", "sqlserver":
// case "postgres", "sqlserver":
default:
// statements are already designed to work with postgresql
return sql
Expand Down
44 changes: 29 additions & 15 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
)

var validBasketName = regexp.MustCompile(basketNamePattern)
var defaultResponse = ResponseConfig{Status: 200, IsTemplate: false}
var defaultResponse = ResponseConfig{Status: 200, Headers: http.Header{}, IsTemplate: false}
var basketPageTemplate = template.Must(template.New("basket").Parse(basketPageContentTemplate))

// writeJSON writes JSON content to HTTP response
Expand Down Expand Up @@ -351,44 +351,58 @@ func AcceptBasketRequests(w http.ResponseWriter, r *http.Request) {
if basket := basketsDb.Get(name); basket != nil {
request := basket.Add(r)

responseConfig := basket.GetResponse(r.Method)
if responseConfig == nil {
responseConfig = &defaultResponse
}

// forward request in separate thread
config := basket.Config()
if len(config.ForwardURL) > 0 && r.Header.Get(DoNotForwardHeader) != "1" {
client := httpClient
if config.InsecureTLS {
go request.Forward(httpInsecureClient, config, name)
} else {
go request.Forward(httpClient, config, name)
client = httpInsecureClient
}
}

// HTTP response
response := basket.GetResponse(r.Method)
if response == nil {
response = &defaultResponse
if config.ProxyResponse {
response := request.Forward(client, config, name)
defer response.Body.Close()

body, err := ioutil.ReadAll(response.Body)
if err != nil {
http.Error(w, "Error in "+err.Error(), http.StatusInternalServerError)
}

responseConfig.Headers = response.Header
responseConfig.Status = response.StatusCode
responseConfig.Body = string(body)
} else {
go request.Forward(client, config, name)
}
}

// headers
for k, v := range response.Headers {
for k, v := range responseConfig.Headers {
w.Header()[k] = v
}
// body
if response.IsTemplate && len(response.Body) > 0 {
if responseConfig.IsTemplate && len(responseConfig.Body) > 0 {
// template
t, err := template.New(name + "-" + r.Method).Parse(response.Body)
t, err := template.New(name + "-" + r.Method).Parse(responseConfig.Body)
if err != nil {
// invalid template
http.Error(w, "Error in "+err.Error(), http.StatusInternalServerError)
} else {
// status
w.WriteHeader(response.Status)
w.WriteHeader(responseConfig.Status)
// templated body
t.Execute(w, r.URL.Query())
}
} else {
// status
w.WriteHeader(response.Status)
w.WriteHeader(responseConfig.Status)
// plain body
w.Write([]byte(response.Body))
w.Write([]byte(responseConfig.Body))
}
} else {
w.WriteHeader(http.StatusNotFound)
Expand Down
50 changes: 48 additions & 2 deletions handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/julienschmidt/httprouter"
"github.com/stretchr/testify/assert"
"io/ioutil"
)

func TestWriteJSON(t *testing.T) {
Expand Down Expand Up @@ -69,7 +70,7 @@ func TestCreateBasket_CustomConfig(t *testing.T) {
basket := "create02"

r, err := http.NewRequest("POST", "http://localhost:55555/baskets/"+basket, strings.NewReader(
"{\"capacity\":30,\"insecure_tls\":true,\"expand_path\":true,\"forward_url\": \"http://localhost:12345/test\"}"))
"{\"capacity\":30,\"insecure_tls\":true,\"expand_path\":true,\"forward_url\": \"http://localhost:12345/test\",\"proxy_response\":true}"))

if assert.NoError(t, err) {
w := httptest.NewRecorder()
Expand All @@ -88,6 +89,7 @@ func TestCreateBasket_CustomConfig(t *testing.T) {
assert.Equal(t, 30, config.Capacity, "wrong basket capacity")
assert.True(t, config.InsecureTLS, "wrong value of Insecure TLS flag")
assert.True(t, config.ExpandPath, "wrong value of Expand Path flag")
assert.True(t, config.ProxyResponse, "wrong value of Proxy Response flag")
assert.Equal(t, "http://localhost:12345/test", config.ForwardURL, "wrong Forward URL")
}
}
Expand Down Expand Up @@ -392,7 +394,7 @@ func TestUpdateBasket(t *testing.T) {
err = json.Unmarshal(w.Body.Bytes(), auth)
if assert.NoError(t, err, "Failed to parse CreateBasket response") {
r, err = http.NewRequest("PUT", "http://localhost:55555/baskets/"+basket,
strings.NewReader("{\"capacity\":50, \"expand_path\":true, \"forward_url\":\"http://test.server/forward\"}"))
strings.NewReader("{\"capacity\":50, \"expand_path\":true, \"forward_url\":\"http://test.server/forward\",\"proxy_response\":true}"))

if assert.NoError(t, err) {
r.Header.Add("Authorization", auth.Token)
Expand All @@ -407,6 +409,7 @@ func TestUpdateBasket(t *testing.T) {
assert.Equal(t, 50, config.Capacity, "wrong basket capacity")
assert.False(t, config.InsecureTLS, "wrong value of Insecure TLS flag")
assert.True(t, config.ExpandPath, "wrong value of Expand Path flag")
assert.True(t, config.ProxyResponse, "wrong value of Proxy Response flag")
assert.Equal(t, "http://test.server/forward", config.ForwardURL, "wrong Forward URL")
}
}
Expand Down Expand Up @@ -1580,3 +1583,46 @@ func TestAcceptBasketRequests_WithForwardExpand(t *testing.T) {
}
}
}

func TestAcceptBasketRequests_WithProxyRequest(t *testing.T) {
basket := "accept07"
method := "DELETE"

// Test HTTP server
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusAccepted)
w.Write([]byte("test"))
}))
defer ts.Close()

// Config to forward requests to test HTTP server (also enable expanding URL)
forwardURL := ts.URL + "/service?from=" + basket

r, err := http.NewRequest("POST", "http://localhost:55555/baskets/"+basket,
strings.NewReader("{\"forward_url\":\""+forwardURL+"\",\"expand_path\":true,\"capacity\":200,\"proxy_response\":true}"))
if assert.NoError(t, err) {
ps := append(make(httprouter.Params, 0), httprouter.Param{Key: "basket", Value: basket})
w := httptest.NewRecorder()

CreateBasket(w, r, ps)
assert.Equal(t, 201, w.Code, "wrong HTTP result code")

r, err = http.NewRequest(method, "http://localhost:55555/"+basket+"/articles/123?sig=abcdge3276542",
strings.NewReader(""))
r.Header.Add("X-Client", "Java/1.8")

if assert.NoError(t, err) {
w = httptest.NewRecorder()
AcceptBasketRequests(w, r)

// read response body
responseBody, err := ioutil.ReadAll(w.Body)
assert.NoError(t, err)

// validate expected response
assert.Equal(t, 202, w.Code, "wrong HTTP response code")
assert.Equal(t, "test", string(responseBody), "wrong response body")
time.Sleep(100 * time.Millisecond)
}
}
}
8 changes: 8 additions & 0 deletions web_basket.html.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,11 +370,13 @@ const (
function updateConfig() {
if (currentConfig && (
currentConfig.forward_url != $("#basket_forward_url").val() ||
currentConfig.proxy_response != $("#basket_proxy_response").prop("checked") ||
currentConfig.expand_path != $("#basket_expand_path").prop("checked") ||
currentConfig.insecure_tls != $("#basket_insecure_tls").prop("checked") ||
currentConfig.capacity != $("#basket_capacity").val()
)) {
currentConfig.forward_url = $("#basket_forward_url").val();
currentConfig.proxy_response = $("#basket_proxy_response").prop("checked");
currentConfig.expand_path = $("#basket_expand_path").prop("checked");
currentConfig.insecure_tls = $("#basket_insecure_tls").prop("checked");
currentConfig.capacity = parseInt($("#basket_capacity").val());
Expand Down Expand Up @@ -428,6 +430,7 @@ const (
if (data) {
currentConfig = data;
$("#basket_forward_url").val(currentConfig.forward_url);
$("#basket_proxy_response").prop("checked", currentConfig.proxy_response);
$("#basket_expand_path").prop("checked", currentConfig.expand_path);
$("#basket_insecure_tls").prop("checked", currentConfig.insecure_tls);
$("#basket_capacity").val(currentConfig.capacity);
Expand Down Expand Up @@ -661,6 +664,11 @@ const (
only affects forwarding to URLs like <kbd>https://...</kbd>
</label>
</div>
<div class="checkbox">
<label><input type="checkbox" id="basket_proxy_response">
<abbr title="Proxies the response from the forward URL back to the client">Proxy Response</abbr>
</label>
</div>
<div class="checkbox">
<label><input type="checkbox" id="basket_expand_path"> Expand Forward Path</label>
</div>
Expand Down

0 comments on commit 5125ec8

Please sign in to comment.