From 61491778acbdc412b18660e46fdee86d506bb672 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Wed, 7 Mar 2018 21:20:21 -0800
Subject: [PATCH 01/13] year update
[ci skip]
---
LICENSE | 2 +-
max_bytes_reader.go.bak | 80 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 81 insertions(+), 1 deletion(-)
create mode 100644 max_bytes_reader.go.bak
diff --git a/LICENSE b/LICENSE
index 491ce72..02ad2ea 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2016-2017 Jeevanandam M., https://myjeeva.com
+Copyright (c) 2016-2018 Jeevanandam M., https://myjeeva.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/max_bytes_reader.go.bak b/max_bytes_reader.go.bak
new file mode 100644
index 0000000..6a93489
--- /dev/null
+++ b/max_bytes_reader.go.bak
@@ -0,0 +1,80 @@
+package ahttp
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "sync"
+)
+
+var maxBytesReaderPool = &sync.Pool{New: func() interface{} { return &maxBytesReader{} }}
+
+// maxBytesReader is a minimal version of net/http package maxBytesReader for aah.
+// so that we do memory pool, much more.
+//
+// MaxBytesReader's result is a ReadCloser, returns a
+// non-EOF error for a Read beyond the limit, and closes the
+// underlying reader when its Close method is called.
+//
+// MaxBytesReader prevents clients from accidentally or maliciously
+// sending a large request and wasting server resources.
+type maxBytesReader struct {
+ w ResponseWriter
+ r io.ReadCloser // underlying reader
+ n int64 // max bytes remaining
+ err error // sticky error
+}
+
+func (mr *maxBytesReader) Read(p []byte) (n int, err error) {
+ if mr.err != nil {
+ return 0, mr.err
+ }
+
+ if len(p) == 0 {
+ return 0, nil
+ }
+
+ // If they asked for a 32KB byte read but only 5 bytes are
+ // remaining, no need to read 32KB. 6 bytes will answer the
+ // question of the whether we hit the limit or go past it.
+ if int64(len(p)) > mr.n+1 {
+ p = p[:mr.n+1]
+ }
+
+ n, err = mr.r.Read(p)
+ if int64(n) <= mr.n {
+ mr.n -= int64(n)
+ mr.err = err
+ return n, err
+ }
+
+ n = int(mr.n)
+ mr.n = 0
+
+ // Set the header to close the connection
+ mr.w.Header().Set(HeaderConnection, "close")
+ mr.err = errors.New("ahttp: request body too large")
+ _ = mr.Close()
+ return n, mr.err
+}
+
+func (mr *maxBytesReader) Close() error {
+ fmt.Println("maxBytesReader close called")
+ return mr.r.Close()
+}
+
+func (mr *maxBytesReader) Reset() {
+ mr.w = nil
+ mr.r = nil
+ mr.n = 0
+ mr.err = nil
+}
+
+func releaseMaxBytesReader(r *Request) {
+ if r.Raw.Body != nil {
+ if mr, ok := r.Raw.Body.(*maxBytesReader); ok {
+ mr.Reset()
+ maxBytesReaderPool.Put(mr)
+ }
+ }
+}
From e9a1693b4fcdd640e9c87b2a9c896f136c79fc07 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Wed, 28 Mar 2018 19:49:42 -0700
Subject: [PATCH 02/13] travis build config update
---
.travis.yml | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 7ee5ebe..6ade40e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,17 +7,16 @@ branches:
# skip tags build, we are building branch and master that is enough for
# consistenty check and release. Let's use Travis CI resources optimally
# for aah framework.
- - /^v[0-9]\.[0-9]/
+ - /^v[0-9.]+$/
go:
- - 1.8
- 1.9
+ - "1.10"
- tip
go_import_path: aahframework.org/ahttp.v0
install:
- - git config --global http.https://aahframework.org.followRedirects true
- go get -t -v ./...
script:
From 42580a03b01423d6eb86f01d28058572bbef18e6 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Sun, 1 Apr 2018 17:55:50 -0700
Subject: [PATCH 03/13] version bump to v0.11.0-edge [ci skip]
---
version.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/version.go b/version.go
index 8ccc555..dca6a6f 100644
--- a/version.go
+++ b/version.go
@@ -5,4 +5,4 @@
package ahttp
// Version no. of aah framework ahttp library
-const Version = "0.10"
+const Version = "0.11.0-edge"
From 82ffdc437a5f280b5551c31310450c4d362df1be Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Sun, 1 Apr 2018 19:43:03 -0700
Subject: [PATCH 04/13] go-aah/aah#157 code and flow optimization on http lib
---
ahttp.go | 8 ++
content_type.go | 29 ++---
gzip_response.go | 18 ++--
header.go | 5 +-
header_test.go | 15 +--
request.go | 273 +++++++++++++++++++++++++++--------------------
request_test.go | 24 +++--
response.go | 60 +++++------
8 files changed, 243 insertions(+), 189 deletions(-)
diff --git a/ahttp.go b/ahttp.go
index 3014658..3a6a028 100644
--- a/ahttp.go
+++ b/ahttp.go
@@ -24,6 +24,13 @@ const (
MethodTrace = http.MethodTrace
)
+// URI Protocol scheme names
+const (
+ SchemeHTTP = "http"
+ SchemeHTTPS = "https"
+ SchemeFTP = "ftp"
+)
+
// TimeFormat is the time format to use when generating times in HTTP
// headers. It is like time.RFC1123 but hard-codes GMT as the time
// zone. The time being formatted must be in UTC for Format to
@@ -53,6 +60,7 @@ func AcquireRequest(r *http.Request) *Request {
// ReleaseRequest method resets the instance value and puts back to pool.
func ReleaseRequest(r *Request) {
if r != nil {
+ r.cleanupMutlipart()
r.Reset()
requestPool.Put(r)
}
diff --git a/content_type.go b/content_type.go
index 6d629af..5b441b9 100644
--- a/content_type.go
+++ b/content_type.go
@@ -36,26 +36,29 @@ var (
// ContentTypeOctetStream content type for bytes.
ContentTypeOctetStream = parseMediaType("application/octet-stream")
-)
-type (
- // ContentType is represents request and response content type values
- ContentType struct {
- Mime string
- Exts []string
- Params map[string]string
- }
+ // ContentTypeJavascript content type.
+ ContentTypeJavascript = parseMediaType("application/javascript; charset=utf-8")
)
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
-// Content-Type methods
+// Content-Type
//___________________________________
-// IsEqual method compares give Content-Type string with current instance.
+// ContentType is represents request and response content type values
+type ContentType struct {
+ Mime string
+ Exts []string
+ Params map[string]string
+}
+
+// IsEqual method returns true if its equals to current content-type instance
+// otherwise false.
// E.g.:
// contentType.IsEqual("application/json")
+// contentType.IsEqual("application/json; charset=utf-8")
func (c *ContentType) IsEqual(contentType string) bool {
- return strings.HasPrefix(c.String(), strings.ToLower(contentType))
+ return strings.HasPrefix(contentType, c.Mime)
}
// Charset method returns charset of content-type
@@ -65,7 +68,7 @@ func (c *ContentType) IsEqual(contentType string) bool {
//
// Method returns `utf-8`
func (c *ContentType) Charset(defaultCharset string) string {
- if v, ok := c.Params["charset"]; ok {
+ if v, found := c.Params["charset"]; found {
return v
}
return defaultCharset
@@ -117,6 +120,6 @@ func (c *ContentType) Raw() string {
}
// String is stringer interface
-func (c *ContentType) String() string {
+func (c ContentType) String() string {
return c.Raw()
}
diff --git a/gzip_response.go b/gzip_response.go
index 2c2928a..ab6c02b 100644
--- a/gzip_response.go
+++ b/gzip_response.go
@@ -15,13 +15,6 @@ import (
"aahframework.org/essentials.v0"
)
-// GzipResponse extends `ahttp.Response` and provides gzip for response
-// bytes before writing them to the underlying response.
-type GzipResponse struct {
- r *Response
- gw *gzip.Writer
-}
-
var (
// GzipLevel holds value from app config.
GzipLevel int
@@ -61,9 +54,16 @@ func PutGzipResponseWiriter(rw ResponseWriter) {
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
-// Response methods
+// GzipResponse
//___________________________________
+// GzipResponse extends `ahttp.Response` to provides gzip compression for response
+// bytes to the underlying response.
+type GzipResponse struct {
+ r *Response
+ gw *gzip.Writer
+}
+
// Status method returns HTTP response status code. If status is not yet written
// it reurns 0.
func (g *GzipResponse) Status() int {
@@ -82,9 +82,7 @@ func (g *GzipResponse) Header() http.Header {
// Write method writes bytes into Response.
func (g *GzipResponse) Write(b []byte) (int, error) {
- g.r.setContentTypeIfNotSet(b)
g.r.WriteHeader(http.StatusOK)
-
size, err := g.gw.Write(b)
g.r.bytesWritten += size
return size, err
diff --git a/header.go b/header.go
index 3680421..fdbfe18 100644
--- a/header.go
+++ b/header.go
@@ -167,10 +167,9 @@ func NegotiateEncoding(req *http.Request) *AcceptSpec {
// ParseContentType method parses the request header `Content-Type` as per RFC1521.
func ParseContentType(req *http.Request) *ContentType {
contentType := req.Header.Get(HeaderContentType)
- if ess.IsStrEmpty(contentType) {
+ if contentType == "" {
return ContentTypeHTML
}
-
return parseMediaType(contentType)
}
@@ -281,7 +280,7 @@ func NewLocale(value string) *Locale {
//___________________________________
// String is stringer interface.
-func (l *Locale) String() string {
+func (l Locale) String() string {
return l.Raw
}
diff --git a/header_test.go b/header_test.go
index 9c834b9..fea468c 100644
--- a/header_test.go
+++ b/header_test.go
@@ -94,26 +94,29 @@ func TestHTTPNegotiateLocale(t *testing.T) {
func TestHTTPNegotiateEncoding(t *testing.T) {
req1 := createRawHTTPRequest(HeaderAcceptEncoding, "compress;q=0.5, gzip;q=1.0")
- encoding := NegotiateEncoding(req1)
+ areq1 := AcquireRequest(req1)
+ encoding := areq1.AcceptEncoding()
+ assert.True(t, areq1.IsGzipAccepted)
assert.Equal(t, "gzip", encoding.Value)
assert.Equal(t, "gzip;q=1.0", encoding.Raw)
- assert.True(t, isGzipAccepted(&Request{}, req1))
req2 := createRawHTTPRequest(HeaderAcceptEncoding, "gzip;q=1.0, identity; q=0.5, *;q=0")
- encoding = NegotiateEncoding(req2)
+ areq2 := AcquireRequest(req2)
+ encoding = areq2.AcceptEncoding()
+ assert.True(t, areq2.IsGzipAccepted)
assert.Equal(t, "gzip", encoding.Value)
assert.Equal(t, "gzip;q=1.0", encoding.Raw)
- assert.True(t, isGzipAccepted(&Request{}, req1))
req3 := createRawHTTPRequest(HeaderAcceptEncoding, "")
encoding = NegotiateEncoding(req3)
assert.Equal(t, true, encoding == nil)
req4 := createRawHTTPRequest(HeaderAcceptEncoding, "compress;q=0.5")
- encoding = NegotiateEncoding(req4)
+ areq4 := AcquireRequest(req4)
+ encoding = areq4.AcceptEncoding()
+ assert.False(t, areq4.IsGzipAccepted)
assert.Equal(t, "compress", encoding.Value)
assert.Equal(t, "compress;q=0.5", encoding.Raw)
- assert.False(t, isGzipAccepted(&Request{}, req4))
}
func TestHTTPAcceptHeaderVendorType(t *testing.T) {
diff --git a/request.go b/request.go
index bef424b..608af31 100644
--- a/request.go
+++ b/request.go
@@ -28,83 +28,6 @@ const (
var requestPool = &sync.Pool{New: func() interface{} { return &Request{} }}
-type (
- // Request is extends `http.Request` for aah framework
- Request struct {
- // Scheme value is protocol; it's a derived value in the order as below.
- // - `X-Forwarded-Proto` is not empty return value as is
- // - `http.Request.TLS` is not nil value is `https`
- // - `http.Request.TLS` is nil value is `http`
- Scheme string
-
- // Host value of the HTTP 'Host' header (e.g. 'example.com:8080').
- Host string
-
- // Proto value of the current HTTP request protocol. (e.g. HTTP/1.1, HTTP/2.0)
- Proto string
-
- // Method request method e.g. `GET`, `POST`, etc.
- Method string
-
- // Path the request URL Path e.g. `/app/login.html`.
- Path string
-
- // Header request HTTP headers
- Header http.Header
-
- // ContentType the parsed value of HTTP header `Content-Type`.
- // Partial implementation as per RFC1521.
- ContentType *ContentType
-
- // AcceptContentType negotiated value from HTTP Header `Accept`.
- // The resolve order is-
- // 1) URL extension
- // 2) Accept header (As per RFC7231 and vendor type as per RFC4288)
- // Most quailfied one based on quality factor otherwise default is HTML.
- AcceptContentType *ContentType
-
- // AcceptEncoding negotiated value from HTTP Header the `Accept-Encoding`
- // As per RFC7231.
- // Most quailfied one based on quality factor.
- AcceptEncoding *AcceptSpec
-
- // Params contains values from Path, Query, Form and File.
- Params *Params
-
- // Referer value of the HTTP 'Referrer' (or 'Referer') header.
- Referer string
-
- // UserAgent value of the HTTP 'User-Agent' header.
- UserAgent string
-
- // ClientIP remote client IP address aka Remote IP. Parsed in the order of
- // `X-Forwarded-For`, `X-Real-IP` and finally `http.Request.RemoteAddr`.
- ClientIP string
-
- // Locale negotiated value from HTTP Header `Accept-Language`.
- // As per RFC7231.
- Locale *Locale
-
- // IsGzipAccepted is true if the HTTP client accepts Gzip response,
- // otherwise false.
- IsGzipAccepted bool
-
- // Raw an object of Go HTTP server, direct interaction with
- // raw object is not encouraged.
- //
- // DEPRECATED: Raw field to be unexported on v1 release, use `Req.Unwarp()` instead.
- Raw *http.Request
- }
-
- // Params structure holds value of Path, Query, Form and File.
- Params struct {
- Path map[string]string
- Query url.Values
- Form url.Values
- File map[string][]*multipart.FileHeader
- }
-)
-
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package methods
//___________________________________
@@ -118,23 +41,114 @@ func ParseRequest(r *http.Request, req *Request) *Request {
req.Method = r.Method
req.Path = r.URL.Path
req.Header = r.Header
- req.ContentType = ParseContentType(r)
- req.AcceptContentType = NegotiateContentType(r)
req.Params = &Params{Query: r.URL.Query()}
req.Referer = getReferer(r.Header)
req.UserAgent = r.Header.Get(HeaderUserAgent)
req.ClientIP = clientIP(r)
- req.Locale = NegotiateLocale(r)
- req.IsGzipAccepted = isGzipAccepted(req, r)
+ req.IsGzipAccepted = strings.Contains(r.Header.Get(HeaderAcceptEncoding), "gzip")
req.Raw = r
return req
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
-// Request methods
+// Request
//___________________________________
+// Request type extends `http.Request` and provides multiple helper methods
+// per industry RFC guideline for aah framework.
+type Request struct {
+ // Scheme value is protocol; it's a derived value in the order as below.
+ // - `X-Forwarded-Proto` is not empty return value as is
+ // - `http.Request.TLS` is not nil value is `https`
+ // - `http.Request.TLS` is nil value is `http`
+ Scheme string
+
+ // Host value of the HTTP 'Host' header (e.g. 'example.com:8080').
+ Host string
+
+ // Proto value of the current HTTP request protocol. (e.g. HTTP/1.1, HTTP/2.0)
+ Proto string
+
+ // Method request method e.g. `GET`, `POST`, etc.
+ Method string
+
+ // Path the request URL Path e.g. `/app/login.html`.
+ Path string
+
+ // Header request HTTP headers
+ Header http.Header
+
+ // Params contains values from Path, Query, Form and File.
+ Params *Params
+
+ // Referer value of the HTTP 'Referrer' (or 'Referer') header.
+ Referer string
+
+ // UserAgent value of the HTTP 'User-Agent' header.
+ UserAgent string
+
+ // ClientIP remote client IP address aka Remote IP. Parsed in the order of
+ // `X-Forwarded-For`, `X-Real-IP` and finally `http.Request.RemoteAddr`.
+ ClientIP string
+
+ // IsGzipAccepted is true if the HTTP client accepts Gzip response,
+ // otherwise false.
+ IsGzipAccepted bool
+
+ // Raw an object of Go HTTP server, direct interaction with
+ // raw object is not encouraged.
+ //
+ // DEPRECATED: Raw field to be unexported on v1 release, use `Req.Unwarp()` instead.
+ Raw *http.Request
+
+ locale *Locale
+ contentType *ContentType
+ acceptContentType *ContentType
+ acceptEncoding *AcceptSpec
+}
+
+// AcceptContentType method returns negotiated value.
+//
+// The resolve order is-
+//
+// 1) URL extension
+//
+// 2) Accept header (As per RFC7231 and vendor type as per RFC4288)
+//
+// Most quailfied one based on quality factor otherwise default is Plain text.
+func (r *Request) AcceptContentType() *ContentType {
+ if r.acceptContentType == nil {
+ r.acceptContentType = NegotiateContentType(r.Unwrap())
+ }
+ return r.acceptContentType
+}
+
+// SetAcceptContentType method is used to set Accept ContentType instance.
+func (r *Request) SetAcceptContentType(contentType *ContentType) *Request {
+ r.acceptContentType = contentType
+ return r
+}
+
+// AcceptEncoding method returns negotiated value from HTTP Header the `Accept-Encoding`
+// As per RFC7231.
+//
+// Most quailfied one based on quality factor.
+func (r *Request) AcceptEncoding() *AcceptSpec {
+ if r.acceptEncoding == nil {
+ if specs := ParseAcceptEncoding(r.Unwrap()); specs != nil {
+ r.acceptEncoding = specs.MostQualified()
+ }
+ }
+ return r.acceptEncoding
+}
+
+// SetAcceptEncoding method is used to accept encoding spec instance.
+func (r *Request) SetAcceptEncoding(encoding *AcceptSpec) *Request {
+ r.acceptEncoding = encoding
+ return r
+}
+
// Cookie method returns a named cookie from HTTP request otherwise error.
func (r *Request) Cookie(name string) (*http.Cookie, error) {
return r.Unwrap().Cookie(name)
@@ -145,6 +159,35 @@ func (r *Request) Cookies() []*http.Cookie {
return r.Unwrap().Cookies()
}
+// ContentType method returns the parsed value of HTTP header `Content-Type` per RFC1521.
+func (r *Request) ContentType() *ContentType {
+ if r.contentType == nil {
+ r.contentType = ParseContentType(r.Unwrap())
+ }
+ return r.contentType
+}
+
+// SetContentType method is used to set ContentType instance.
+func (r *Request) SetContentType(contType *ContentType) *Request {
+ r.contentType = contType
+ return r
+}
+
+// Locale method returns negotiated value from HTTP Header `Accept-Language`
+// per RFC7231.
+func (r *Request) Locale() *Locale {
+ if r.locale == nil {
+ r.locale = NegotiateLocale(r.Unwrap())
+ }
+ return r.locale
+}
+
+// SetLocale method is used to set locale instance in to aah request.
+func (r *Request) SetLocale(locale *Locale) *Request {
+ r.locale = locale
+ return r
+}
+
// IsJSONP method returns true if request URL query string has "callback=function_name".
// otherwise false.
func (r *Request) IsJSONP() bool {
@@ -162,6 +205,11 @@ func (r *Request) IsWebSocket() bool {
return r.Header.Get(HeaderUpgrade) == websocketHeaderValue
}
+// URL method return underlying request URL instance.
+func (r *Request) URL() *url.URL {
+ return r.Unwrap().URL
+}
+
// PathValue method returns value for given Path param key otherwise empty string.
// For eg.: /users/:userId => PathValue("userId")
func (r *Request) PathValue(key string) string {
@@ -202,7 +250,8 @@ func (r *Request) Body() io.ReadCloser {
return r.Unwrap().Body
}
-// Unwrap method returns the underlying *http.Request.
+// Unwrap method returns the underlying *http.Request instance of Go HTTP server,
+// direct interaction with raw object is not encouraged. Use it appropriately.
func (r *Request) Unwrap() *http.Request {
return r.Raw
}
@@ -266,22 +315,37 @@ func (r *Request) Reset() {
r.Method = ""
r.Path = ""
r.Header = nil
- r.ContentType = nil
- r.AcceptContentType = nil
- r.AcceptEncoding = nil
r.Params = nil
r.Referer = ""
r.UserAgent = ""
r.ClientIP = ""
- r.Locale = nil
r.IsGzipAccepted = false
r.Raw = nil
+
+ r.locale = nil
+ r.contentType = nil
+ r.acceptContentType = nil
+ r.acceptEncoding = nil
+}
+
+func (r *Request) cleanupMutlipart() {
+ if r.Unwrap().MultipartForm != nil {
+ r.Unwrap().MultipartForm.RemoveAll()
+ }
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Params methods
//___________________________________
+// Params structure holds value of Path, Query, Form and File.
+type Params struct {
+ Path map[string]string
+ Query url.Values
+ Form url.Values
+ File map[string][]*multipart.FileHeader
+}
+
// PathValue method returns value for given Path param key otherwise empty string.
// For eg.: `/users/:userId` => `PathValue("userId")`.
func (p *Params) PathValue(key string) string {
@@ -351,15 +415,13 @@ func (p *Params) FormFile(key string) (multipart.File, *multipart.FileHeader, er
// - `http.Request.TLS` is nil value is `http`
func identifyScheme(r *http.Request) string {
scheme := r.Header.Get(HeaderXForwardedProto)
- if !ess.IsStrEmpty(scheme) {
- return scheme
- }
-
- if r.TLS != nil {
- return "https"
+ if scheme == "" {
+ if r.TLS == nil {
+ return SchemeHTTP // "http"
+ }
+ return SchemeHTTPS // "https"
}
-
- return "http"
+ return scheme
}
// clientIP returns IP address from HTTP request, typically known as Client IP or
@@ -389,7 +451,7 @@ func clientIP(req *http.Request) string {
}
func host(r *http.Request) string {
- if ess.IsStrEmpty(r.URL.Host) {
+ if r.URL.Host == "" {
return r.Host
}
return r.URL.Host
@@ -397,27 +459,12 @@ func host(r *http.Request) string {
func getReferer(hdr http.Header) string {
referer := hdr.Get(HeaderReferer)
-
- if ess.IsStrEmpty(referer) {
- referer = hdr.Get("Referrer")
+ if referer == "" {
+ return hdr.Get("Referrer")
}
-
return referer
}
-func isGzipAccepted(req *Request, r *http.Request) bool {
- specs := ParseAcceptEncoding(r)
- if specs != nil {
- req.AcceptEncoding = specs.MostQualified()
- for _, v := range specs {
- if v.Value == "gzip" {
- return true
- }
- }
- }
- return false
-}
-
func saveFile(r io.Reader, destFile string) (int64, error) {
f, err := os.OpenFile(destFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
diff --git a/request_test.go b/request_test.go
index 107c930..c36af08 100644
--- a/request_test.go
+++ b/request_test.go
@@ -62,13 +62,14 @@ func TestHTTPParseRequest(t *testing.T) {
req.URL, _ = url.Parse("/welcome1.html?_ref=true")
aahReq := AcquireRequest(req)
+ assert.True(t, req.URL == aahReq.URL())
assert.Equal(t, req, aahReq.Unwrap())
assert.Equal(t, "127.0.0.1:8080", aahReq.Host)
assert.Equal(t, MethodGet, aahReq.Method)
assert.Equal(t, "/welcome1.html", aahReq.Path)
assert.Equal(t, "en-gb;leve=1;q=0.8, da, en;level=2;q=0.7, en-us;q=gg", aahReq.Header.Get(HeaderAcceptLanguage))
- assert.Equal(t, "application/json; charset=utf-8", aahReq.ContentType.String())
+ assert.Equal(t, "application/json; charset=utf-8", aahReq.ContentType().String())
assert.Equal(t, "192.168.0.1", aahReq.ClientIP)
assert.Equal(t, "http://localhost:8080/home.html", aahReq.Referer)
@@ -92,17 +93,22 @@ func TestHTTPParseRequest(t *testing.T) {
assert.False(t, aahReq.IsAJAX())
assert.False(t, aahReq.IsWebSocket())
- // Reset it
- aahReq.Reset()
+ aahReq.SetAcceptContentType(nil)
+ assert.NotNil(t, aahReq.AcceptContentType())
+ aahReq.SetLocale(nil)
+ assert.NotNil(t, aahReq.Locale())
+ aahReq.SetContentType(nil)
+ assert.NotNil(t, aahReq.ContentType())
+ aahReq.SetAcceptEncoding(nil)
+ assert.Nil(t, aahReq.AcceptEncoding())
+
+ // Release it
+ ReleaseRequest(aahReq)
assert.Nil(t, aahReq.Header)
- assert.Nil(t, aahReq.ContentType)
- assert.Nil(t, aahReq.AcceptContentType)
assert.Nil(t, aahReq.Params)
- assert.Nil(t, aahReq.Locale)
assert.Nil(t, aahReq.Raw)
- assert.True(t, len(aahReq.UserAgent) == 0)
- assert.True(t, len(aahReq.ClientIP) == 0)
- ReleaseRequest(aahReq)
+ assert.True(t, aahReq.UserAgent == "")
+ assert.True(t, aahReq.ClientIP == "")
}
func TestHTTPRequestParams(t *testing.T) {
diff --git a/response.go b/response.go
index 6dc4807..c1b4cb5 100644
--- a/response.go
+++ b/response.go
@@ -15,32 +15,6 @@ import (
"aahframework.org/essentials.v0"
)
-type (
- // ResponseWriter extends the `http.ResponseWriter` interface to implements
- // aah framework response.
- ResponseWriter interface {
- http.ResponseWriter
-
- // Status returns the HTTP status of the request otherwise 0
- Status() int
-
- // BytesWritten returns the total number of bytes written
- BytesWritten() int
-
- // Unwrap returns the original `ResponseWriter`
- Unwrap() http.ResponseWriter
- }
-
- // Response implements multiple interface (CloseNotifier, Flusher,
- // Hijacker) and handy methods for aah framework.
- Response struct {
- w http.ResponseWriter
- status int
- wroteStatus bool
- bytesWritten int
- }
-)
-
var (
responsePool = &sync.Pool{New: func() interface{} { return &Response{} }}
@@ -53,6 +27,21 @@ var (
_ ResponseWriter = (*Response)(nil)
)
+// ResponseWriter extends the `http.ResponseWriter` interface to implements
+// aah framework response.
+type ResponseWriter interface {
+ http.ResponseWriter
+
+ // Status returns the HTTP status of the request otherwise 0
+ Status() int
+
+ // BytesWritten returns the total number of bytes written
+ BytesWritten() int
+
+ // Unwrap returns the original `ResponseWriter`
+ Unwrap() http.ResponseWriter
+}
+
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package methods
//___________________________________
@@ -74,9 +63,18 @@ func PutResponseWriter(aw ResponseWriter) {
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
-// Response methods
+// Response
//___________________________________
+// Response implements multiple interface (CloseNotifier, Flusher,
+// Hijacker) and handy methods for aah framework.
+type Response struct {
+ w http.ResponseWriter
+ status int
+ wroteStatus bool
+ bytesWritten int
+}
+
// Status method returns HTTP response status code. If status is not yet written
// it reurns 0.
func (r *Response) Status() int {
@@ -99,9 +97,7 @@ func (r *Response) Header() http.Header {
// Write method writes bytes into Response.
func (r *Response) Write(b []byte) (int, error) {
- r.setContentTypeIfNotSet(b)
r.WriteHeader(http.StatusOK)
-
size, err := r.w.Write(b)
r.bytesWritten += size
return size, err
@@ -170,12 +166,6 @@ func (r *Response) Reset() {
// Response Unexported methods
//___________________________________
-func (r *Response) setContentTypeIfNotSet(b []byte) {
- if ct := r.Header().Get(HeaderContentType); ess.IsStrEmpty(ct) {
- r.Header().Set(HeaderContentType, http.DetectContentType(b))
- }
-}
-
// releaseResponse method puts response back to pool.
func releaseResponse(r *Response) {
_ = r.Close()
From cea5ad0cfa4ab4d30ab8788113959e6477c0f7c6 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Mon, 2 Apr 2018 21:38:46 -0700
Subject: [PATCH 05/13] go-aah/aah#157 added path param type and refactored
client ip to method
---
request.go | 100 ++++++++++++++++++++++++++++--------------------
request_test.go | 18 +++++----
2 files changed, 69 insertions(+), 49 deletions(-)
diff --git a/request.go b/request.go
index 608af31..bc57234 100644
--- a/request.go
+++ b/request.go
@@ -21,9 +21,10 @@ import (
)
const (
- jsonpReqParamKey = "callback"
- ajaxHeaderValue = "XMLHttpRequest"
- websocketHeaderValue = "websocket"
+ jsonpReqParamKey = "callback"
+ ajaxHeaderValue = "XMLHttpRequest"
+ wsHdrVal = "websocket"
+ connHdrValPartial = "upgrade"
)
var requestPool = &sync.Pool{New: func() interface{} { return &Request{} }}
@@ -44,7 +45,6 @@ func ParseRequest(r *http.Request, req *Request) *Request {
req.Params = &Params{Query: r.URL.Query()}
req.Referer = getReferer(r.Header)
req.UserAgent = r.Header.Get(HeaderUserAgent)
- req.ClientIP = clientIP(r)
req.IsGzipAccepted = strings.Contains(r.Header.Get(HeaderAcceptEncoding), "gzip")
req.Raw = r
@@ -88,10 +88,6 @@ type Request struct {
// UserAgent value of the HTTP 'User-Agent' header.
UserAgent string
- // ClientIP remote client IP address aka Remote IP. Parsed in the order of
- // `X-Forwarded-For`, `X-Real-IP` and finally `http.Request.RemoteAddr`.
- ClientIP string
-
// IsGzipAccepted is true if the HTTP client accepts Gzip response,
// otherwise false.
IsGzipAccepted bool
@@ -149,6 +145,36 @@ func (r *Request) SetAcceptEncoding(encoding *AcceptSpec) *Request {
return r
}
+// ClientIP method returns remote client IP address aka Remote IP.
+// It parses in the order of `X-Forwarded-For`, `X-Real-IP` and
+// finally `http.Request.RemoteAddr`.
+//
+// Note: Make these header configurable from aah.conf so that aah user
+// could configure headers of their choice. For e.g.
+// X-Appengine-Remote-Addr, etc.
+func (r *Request) ClientIP() string {
+ // Header X-Forwarded-For
+ if fwdFor := r.Header.Get(HeaderXForwardedFor); !ess.IsStrEmpty(fwdFor) {
+ index := strings.Index(fwdFor, ",")
+ if index == -1 {
+ return strings.TrimSpace(fwdFor)
+ }
+ return strings.TrimSpace(fwdFor[:index])
+ }
+
+ // Header X-Real-Ip
+ if realIP := r.Header.Get(HeaderXRealIP); !ess.IsStrEmpty(realIP) {
+ return strings.TrimSpace(realIP)
+ }
+
+ // Remote Address
+ if remoteAddr, _, err := net.SplitHostPort(r.Unwrap().RemoteAddr); err == nil {
+ return strings.TrimSpace(remoteAddr)
+ }
+
+ return ""
+}
+
// Cookie method returns a named cookie from HTTP request otherwise error.
func (r *Request) Cookie(name string) (*http.Cookie, error) {
return r.Unwrap().Cookie(name)
@@ -202,7 +228,8 @@ func (r *Request) IsAJAX() bool {
// IsWebSocket method returns true if request is WebSocket otherwise false.
func (r *Request) IsWebSocket() bool {
- return r.Header.Get(HeaderUpgrade) == websocketHeaderValue
+ return r.Header.Get(HeaderUpgrade) == wsHdrVal &&
+ strings.Contains(strings.ToLower(r.Header.Get(HeaderConnection)), connHdrValPartial)
}
// URL method return underlying request URL instance.
@@ -318,7 +345,6 @@ func (r *Request) Reset() {
r.Params = nil
r.Referer = ""
r.UserAgent = ""
- r.ClientIP = ""
r.IsGzipAccepted = false
r.Raw = nil
@@ -335,12 +361,32 @@ func (r *Request) cleanupMutlipart() {
}
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
-// Params methods
+// PathParams
+//___________________________________
+
+// PathParams struct holds the path parameter key and values.
+type PathParams map[string]string
+
+// Get method returns the value for the given key otherwise empty string.
+func (p PathParams) Get(key string) string {
+ if value, found := p[key]; found {
+ return value
+ }
+ return ""
+}
+
+// Len method returns count of total no. of values.
+func (p PathParams) Len() int {
+ return len(p)
+}
+
+//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+// Params
//___________________________________
// Params structure holds value of Path, Query, Form and File.
type Params struct {
- Path map[string]string
+ Path PathParams
Query url.Values
Form url.Values
File map[string][]*multipart.FileHeader
@@ -350,9 +396,7 @@ type Params struct {
// For eg.: `/users/:userId` => `PathValue("userId")`.
func (p *Params) PathValue(key string) string {
if p.Path != nil {
- if value, found := p.Path[key]; found {
- return value
- }
+ return p.Path.Get(key)
}
return ""
}
@@ -424,32 +468,6 @@ func identifyScheme(r *http.Request) string {
return scheme
}
-// clientIP returns IP address from HTTP request, typically known as Client IP or
-// Remote IP. It parses the IP in the order of X-Forwarded-For, X-Real-IP
-// and finally `http.Request.RemoteAddr`.
-func clientIP(req *http.Request) string {
- // Header X-Forwarded-For
- if fwdFor := req.Header.Get(HeaderXForwardedFor); !ess.IsStrEmpty(fwdFor) {
- index := strings.Index(fwdFor, ",")
- if index == -1 {
- return strings.TrimSpace(fwdFor)
- }
- return strings.TrimSpace(fwdFor[:index])
- }
-
- // Header X-Real-Ip
- if realIP := req.Header.Get(HeaderXRealIP); !ess.IsStrEmpty(realIP) {
- return strings.TrimSpace(realIP)
- }
-
- // Remote Address
- if remoteAddr, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
- return strings.TrimSpace(remoteAddr)
- }
-
- return ""
-}
-
func host(r *http.Request) string {
if r.URL.Host == "" {
return r.Host
diff --git a/request_test.go b/request_test.go
index c36af08..fbb59a5 100644
--- a/request_test.go
+++ b/request_test.go
@@ -21,23 +21,23 @@ import (
func TestHTTPClientIP(t *testing.T) {
req1 := createRawHTTPRequest(HeaderXForwardedFor, "10.0.0.1, 10.0.0.2")
- ipAddress := clientIP(req1)
+ ipAddress := AcquireRequest(req1).ClientIP()
assert.Equal(t, "10.0.0.1", ipAddress)
req2 := createRawHTTPRequest(HeaderXForwardedFor, "10.0.0.2")
- ipAddress = clientIP(req2)
+ ipAddress = AcquireRequest(req2).ClientIP()
assert.Equal(t, "10.0.0.2", ipAddress)
req3 := createRawHTTPRequest(HeaderXRealIP, "10.0.0.3")
- ipAddress = clientIP(req3)
+ ipAddress = AcquireRequest(req3).ClientIP()
assert.Equal(t, "10.0.0.3", ipAddress)
req4 := createRequestWithHost("127.0.0.1:8080", "192.168.0.1:1234")
- ipAddress = clientIP(req4)
+ ipAddress = AcquireRequest(req4).ClientIP()
assert.Equal(t, "192.168.0.1", ipAddress)
req5 := createRequestWithHost("127.0.0.1:8080", "")
- ipAddress = clientIP(req5)
+ ipAddress = AcquireRequest(req5).ClientIP()
assert.Equal(t, "", ipAddress)
}
@@ -70,7 +70,7 @@ func TestHTTPParseRequest(t *testing.T) {
assert.Equal(t, "/welcome1.html", aahReq.Path)
assert.Equal(t, "en-gb;leve=1;q=0.8, da, en;level=2;q=0.7, en-us;q=gg", aahReq.Header.Get(HeaderAcceptLanguage))
assert.Equal(t, "application/json; charset=utf-8", aahReq.ContentType().String())
- assert.Equal(t, "192.168.0.1", aahReq.ClientIP)
+ assert.Equal(t, "192.168.0.1", aahReq.ClientIP())
assert.Equal(t, "http://localhost:8080/home.html", aahReq.Referer)
// Query Value
@@ -108,7 +108,6 @@ func TestHTTPParseRequest(t *testing.T) {
assert.Nil(t, aahReq.Params)
assert.Nil(t, aahReq.Raw)
assert.True(t, aahReq.UserAgent == "")
- assert.True(t, aahReq.ClientIP == "")
}
func TestHTTPRequestParams(t *testing.T) {
@@ -126,6 +125,7 @@ func TestHTTPRequestParams(t *testing.T) {
assert.True(t, len(params1.QueryArrayValue("not-exists")) == 0)
assert.Equal(t, "100001", params1.PathValue("userId"))
assert.Equal(t, "", params1.PathValue("accountId"))
+ assert.Equal(t, 1, params1.Path.Len())
// Form value
form := url.Values{}
@@ -147,6 +147,7 @@ func TestHTTPRequestParams(t *testing.T) {
assert.Equal(t, "Test1", params2.FormArrayValue("names")[0])
assert.Equal(t, "Test 2 value", params2.FormArrayValue("names")[1])
assert.True(t, len(params2.FormArrayValue("not-exists")) == 0)
+ assert.Equal(t, 0, params2.Path.Len())
ReleaseRequest(aahReq2)
// File value
@@ -307,7 +308,8 @@ func TestRequestSaveFileForExistingFile(t *testing.T) {
//___________________________________
func createRequestWithHost(host, remote string) *http.Request {
- return &http.Request{Host: host, RemoteAddr: remote, Header: http.Header{}}
+ url, _ := url.Parse("http://localhost:8080/testpath")
+ return &http.Request{URL: url, Host: host, RemoteAddr: remote, Header: http.Header{}}
}
func setUpRequestSaveFile(t *testing.T) (*Request, string, func()) {
From f339ceaa8bcd868078d6e82c65c552b74365068a Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Sun, 8 Apr 2018 17:17:44 -0700
Subject: [PATCH 06/13] response closer without allocation
---
response.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/response.go b/response.go
index c1b4cb5..5992088 100644
--- a/response.go
+++ b/response.go
@@ -11,8 +11,6 @@ import (
"net"
"net/http"
"sync"
-
- "aahframework.org/essentials.v0"
)
var (
@@ -110,7 +108,9 @@ func (r *Response) BytesWritten() int {
// Close method closes the writer if possible.
func (r *Response) Close() error {
- ess.CloseQuietly(r.w)
+ if w, ok := r.w.(io.Closer); ok {
+ return w.Close()
+ }
return nil
}
From 001dd4148fd974e7a4aabdd7f74eb03cbbb9c649 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Thu, 19 Apr 2018 23:27:21 -0700
Subject: [PATCH 07/13] go-aah/aah#126 updates for websocket and around it
---
ahttp.go | 55 ++++++++++++++++++++++++++++++++++++++++++++
content_type.go | 3 +++
request.go | 61 ++++---------------------------------------------
request_test.go | 9 ++++----
4 files changed, 67 insertions(+), 61 deletions(-)
diff --git a/ahttp.go b/ahttp.go
index 3a6a028..0f71c09 100644
--- a/ahttp.go
+++ b/ahttp.go
@@ -8,7 +8,11 @@ package ahttp
import (
"io"
+ "net"
"net/http"
+ "strings"
+
+ "aahframework.org/essentials.v0"
)
// HTTP Method names
@@ -91,3 +95,54 @@ func WrapGzipWriter(w io.Writer) ResponseWriter {
gr.r = w.(*Response)
return gr
}
+
+// IdentifyScheme method is to identify value of protocol value. It's is derived
+// one, Go language doesn't provide directly.
+// - `X-Forwarded-Proto` is not empty return value as is
+// - `http.Request.TLS` is not nil value is `https`
+// - `http.Request.TLS` is nil value is `http`
+func IdentifyScheme(r *http.Request) string {
+ scheme := r.Header.Get("X-Forwarded-Proto")
+ if scheme == "" {
+ if r.TLS == nil {
+ return "http"
+ }
+ return "https"
+ }
+ return scheme
+}
+
+// IdentifyHost method is to correct Hosyt source value from HTTP request.
+func IdentifyHost(r *http.Request) string {
+ if r.URL.Host == "" {
+ return r.Host
+ }
+ return r.URL.Host
+}
+
+// ClientIP method returns remote Client IP address aka Remote IP.
+// It parses in the order of given set of headers otherwise it uses default
+// default header set `X-Forwarded-For`, `X-Real-IP`, "X-Appengine-Remote-Addr"
+// and finally `http.Request.RemoteAddr`.
+func ClientIP(r *http.Request, hdrs ...string) string {
+ if len(hdrs) == 0 {
+ hdrs = []string{"X-Forwarded-For", "X-Real-IP", "X-Appengine-Remote-Addr"}
+ }
+
+ for _, hdrKey := range hdrs {
+ if hv := r.Header.Get(hdrKey); !ess.IsStrEmpty(hv) {
+ index := strings.Index(hv, ",")
+ if index == -1 {
+ return strings.TrimSpace(hv)
+ }
+ return strings.TrimSpace(hv[:index])
+ }
+ }
+
+ // Remote Address
+ if remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
+ return strings.TrimSpace(remoteAddr)
+ }
+
+ return ""
+}
diff --git a/content_type.go b/content_type.go
index 5b441b9..eeb4257 100644
--- a/content_type.go
+++ b/content_type.go
@@ -39,6 +39,9 @@ var (
// ContentTypeJavascript content type.
ContentTypeJavascript = parseMediaType("application/javascript; charset=utf-8")
+
+ // ContentTypeEventStream Server-Sent Events content type.
+ ContentTypeEventStream = parseMediaType("text/event-stream")
)
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
diff --git a/request.go b/request.go
index bc57234..b41c0e5 100644
--- a/request.go
+++ b/request.go
@@ -9,7 +9,6 @@ import (
"fmt"
"io"
"mime/multipart"
- "net"
"net/http"
"net/url"
"os"
@@ -21,10 +20,8 @@ import (
)
const (
- jsonpReqParamKey = "callback"
- ajaxHeaderValue = "XMLHttpRequest"
- wsHdrVal = "websocket"
- connHdrValPartial = "upgrade"
+ jsonpReqParamKey = "callback"
+ ajaxHeaderValue = "XMLHttpRequest"
)
var requestPool = &sync.Pool{New: func() interface{} { return &Request{} }}
@@ -36,8 +33,8 @@ var requestPool = &sync.Pool{New: func() interface{} { return &Request{} }}
// ParseRequest method populates the given aah framework `ahttp.Request`
// instance from Go HTTP request.
func ParseRequest(r *http.Request, req *Request) *Request {
- req.Scheme = identifyScheme(r)
- req.Host = host(r)
+ req.Scheme = IdentifyScheme(r)
+ req.Host = IdentifyHost(r)
req.Proto = r.Proto
req.Method = r.Method
req.Path = r.URL.Path
@@ -153,26 +150,7 @@ func (r *Request) SetAcceptEncoding(encoding *AcceptSpec) *Request {
// could configure headers of their choice. For e.g.
// X-Appengine-Remote-Addr, etc.
func (r *Request) ClientIP() string {
- // Header X-Forwarded-For
- if fwdFor := r.Header.Get(HeaderXForwardedFor); !ess.IsStrEmpty(fwdFor) {
- index := strings.Index(fwdFor, ",")
- if index == -1 {
- return strings.TrimSpace(fwdFor)
- }
- return strings.TrimSpace(fwdFor[:index])
- }
-
- // Header X-Real-Ip
- if realIP := r.Header.Get(HeaderXRealIP); !ess.IsStrEmpty(realIP) {
- return strings.TrimSpace(realIP)
- }
-
- // Remote Address
- if remoteAddr, _, err := net.SplitHostPort(r.Unwrap().RemoteAddr); err == nil {
- return strings.TrimSpace(remoteAddr)
- }
-
- return ""
+ return ClientIP(r.Unwrap())
}
// Cookie method returns a named cookie from HTTP request otherwise error.
@@ -226,12 +204,6 @@ func (r *Request) IsAJAX() bool {
return r.Header.Get(HeaderXRequestedWith) == ajaxHeaderValue
}
-// IsWebSocket method returns true if request is WebSocket otherwise false.
-func (r *Request) IsWebSocket() bool {
- return r.Header.Get(HeaderUpgrade) == wsHdrVal &&
- strings.Contains(strings.ToLower(r.Header.Get(HeaderConnection)), connHdrValPartial)
-}
-
// URL method return underlying request URL instance.
func (r *Request) URL() *url.URL {
return r.Unwrap().URL
@@ -452,29 +424,6 @@ func (p *Params) FormFile(key string) (multipart.File, *multipart.FileHeader, er
// Unexported methods
//___________________________________
-// identifyScheme method is to identify value of protocol value. It's is derived
-// one, Go language doesn't provide directly.
-// - `X-Forwarded-Proto` is not empty return value as is
-// - `http.Request.TLS` is not nil value is `https`
-// - `http.Request.TLS` is nil value is `http`
-func identifyScheme(r *http.Request) string {
- scheme := r.Header.Get(HeaderXForwardedProto)
- if scheme == "" {
- if r.TLS == nil {
- return SchemeHTTP // "http"
- }
- return SchemeHTTPS // "https"
- }
- return scheme
-}
-
-func host(r *http.Request) string {
- if r.URL.Host == "" {
- return r.Host
- }
- return r.URL.Host
-}
-
func getReferer(hdr http.Header) string {
referer := hdr.Get(HeaderReferer)
if referer == "" {
diff --git a/request_test.go b/request_test.go
index fbb59a5..5967866 100644
--- a/request_test.go
+++ b/request_test.go
@@ -91,7 +91,6 @@ func TestHTTPParseRequest(t *testing.T) {
assert.Nil(t, err)
assert.False(t, aahReq.IsJSONP())
assert.False(t, aahReq.IsAJAX())
- assert.False(t, aahReq.IsWebSocket())
aahReq.SetAcceptContentType(nil)
assert.NotNil(t, aahReq.AcceptContentType())
@@ -186,19 +185,19 @@ func TestHTTPRequestCookies(t *testing.T) {
func TestRequestSchemeDerived(t *testing.T) {
req := httptest.NewRequest("GET", "http://127.0.0.1:8080/welcome.html", nil)
- scheme1 := identifyScheme(req)
+ scheme1 := IdentifyScheme(req)
assert.Equal(t, "http", scheme1)
req.TLS = &tls.ConnectionState{}
- scheme2 := identifyScheme(req)
+ scheme2 := IdentifyScheme(req)
assert.Equal(t, "https", scheme2)
req.Header.Set(HeaderXForwardedProto, "https")
- scheme3 := identifyScheme(req)
+ scheme3 := IdentifyScheme(req)
assert.Equal(t, "https", scheme3)
req.Header.Set(HeaderXForwardedProto, "http")
- scheme4 := identifyScheme(req)
+ scheme4 := IdentifyScheme(req)
assert.Equal(t, "http", scheme4)
}
From 34a1739ecaafee51d50dd9e109ca2fdd54e4a04b Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Tue, 8 May 2018 20:51:53 -0700
Subject: [PATCH 08/13] added X-Forwarded-Protocol, X-Forwarded-Ssl,
X-Url-Scheme improve more coverage on identifying request scheme
---
ahttp.go | 37 ++++++++++++++++++++++++-------------
content_type_test.go | 8 ++++++--
header.go | 3 +++
request.go | 4 ++--
request_test.go | 25 +++++++++++++++----------
5 files changed, 50 insertions(+), 27 deletions(-)
diff --git a/ahttp.go b/ahttp.go
index 0f71c09..1684adc 100644
--- a/ahttp.go
+++ b/ahttp.go
@@ -96,24 +96,35 @@ func WrapGzipWriter(w io.Writer) ResponseWriter {
return gr
}
-// IdentifyScheme method is to identify value of protocol value. It's is derived
+// Scheme method is to identify value of protocol value. It's is derived
// one, Go language doesn't provide directly.
-// - `X-Forwarded-Proto` is not empty return value as is
-// - `http.Request.TLS` is not nil value is `https`
-// - `http.Request.TLS` is nil value is `http`
-func IdentifyScheme(r *http.Request) string {
- scheme := r.Header.Get("X-Forwarded-Proto")
- if scheme == "" {
- if r.TLS == nil {
- return "http"
- }
+// - `X-Forwarded-Proto` is not empty, returns as-is
+// - `X-Forwarded-Protocol` is not empty, returns as-is
+// - `http.Request.TLS` is not nil or `X-Forwarded-Ssl == on` returns `https`
+// - `X-Url-Scheme` is not empty, returns as-is
+// - returns `http`
+func Scheme(r *http.Request) string {
+ if scheme := r.Header.Get(HeaderXForwardedProto); scheme != "" {
+ return scheme
+ }
+
+ if scheme := r.Header.Get(HeaderXForwardedProtocol); scheme != "" {
+ return scheme
+ }
+
+ if r.TLS != nil || r.Header.Get(HeaderXForwardedSsl) == "on" {
return "https"
}
- return scheme
+
+ if scheme := r.Header.Get(HeaderXUrlScheme); scheme != "" {
+ return scheme
+ }
+
+ return "http"
}
-// IdentifyHost method is to correct Hosyt source value from HTTP request.
-func IdentifyHost(r *http.Request) string {
+// Host method is to correct Hosyt source value from HTTP request.
+func Host(r *http.Request) string {
if r.URL.Host == "" {
return r.Host
}
diff --git a/content_type_test.go b/content_type_test.go
index 768abfc..5542bd7 100644
--- a/content_type_test.go
+++ b/content_type_test.go
@@ -6,6 +6,7 @@ package ahttp
import (
"net/url"
+ "runtime"
"testing"
"aahframework.org/essentials.v0"
@@ -41,8 +42,11 @@ func TestHTTPNegotiateContentType(t *testing.T) {
req := createRawHTTPRequest(HeaderAccept, "application/json")
req.URL, _ = url.Parse("http://localhost:8080/testpath.json")
contentType = NegotiateContentType(req)
- assert.True(t, contentType.IsEqual("application/json"))
- assert.Equal(t, ".json", contentType.Exts[0])
+ if runtime.GOOS != "windows" { // due to mime types not exists
+ assert.NotNil(t, contentType)
+ assert.True(t, contentType.IsEqual("application/json"))
+ assert.Equal(t, ".json", contentType.Exts[0])
+ }
req = createRawHTTPRequest(HeaderAccept, "application/json")
req.URL, _ = url.Parse("http://localhost:8080/testpath.html")
diff --git a/header.go b/header.go
index fdbfe18..a9d6c03 100644
--- a/header.go
+++ b/header.go
@@ -79,6 +79,9 @@ const (
HeaderXForwardedHost = "X-Forwarded-Host"
HeaderXForwardedPort = "X-Forwarded-Port"
HeaderXForwardedProto = "X-Forwarded-Proto"
+ HeaderXForwardedProtocol = "X-Forwarded-Protocol"
+ HeaderXForwardedSsl = "X-Forwarded-Ssl"
+ HeaderXUrlScheme = "X-Url-Scheme"
HeaderXForwardedServer = "X-Forwarded-Server"
HeaderXFrameOptions = "X-Frame-Options"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
diff --git a/request.go b/request.go
index b41c0e5..dc0e68f 100644
--- a/request.go
+++ b/request.go
@@ -33,8 +33,8 @@ var requestPool = &sync.Pool{New: func() interface{} { return &Request{} }}
// ParseRequest method populates the given aah framework `ahttp.Request`
// instance from Go HTTP request.
func ParseRequest(r *http.Request, req *Request) *Request {
- req.Scheme = IdentifyScheme(r)
- req.Host = IdentifyHost(r)
+ req.Scheme = Scheme(r)
+ req.Host = Host(r)
req.Proto = r.Proto
req.Method = r.Method
req.Path = r.URL.Path
diff --git a/request_test.go b/request_test.go
index 5967866..3439571 100644
--- a/request_test.go
+++ b/request_test.go
@@ -158,7 +158,7 @@ func TestHTTPRequestParams(t *testing.T) {
f, fh, err := aahReq3.FormFile("testfile.txt")
assert.Nil(t, f)
assert.Equal(t, "testfile.txt", fh.Filename)
- assert.Equal(t, "open : no such file or directory", err.Error())
+ assert.True(t, strings.HasPrefix(err.Error(), "open :"))
ReleaseRequest(aahReq3)
}
@@ -185,20 +185,25 @@ func TestHTTPRequestCookies(t *testing.T) {
func TestRequestSchemeDerived(t *testing.T) {
req := httptest.NewRequest("GET", "http://127.0.0.1:8080/welcome.html", nil)
- scheme1 := IdentifyScheme(req)
- assert.Equal(t, "http", scheme1)
+ assert.Equal(t, "http", Scheme(req))
+
+ req.Header.Set(HeaderXUrlScheme, "http")
+ assert.Equal(t, "http", Scheme(req))
+
+ req.Header.Set(HeaderXForwardedSsl, "on")
+ assert.Equal(t, "https", Scheme(req))
req.TLS = &tls.ConnectionState{}
- scheme2 := IdentifyScheme(req)
- assert.Equal(t, "https", scheme2)
+ assert.Equal(t, "https", Scheme(req))
+
+ req.Header.Set(HeaderXForwardedProtocol, "https")
+ assert.Equal(t, "https", Scheme(req))
req.Header.Set(HeaderXForwardedProto, "https")
- scheme3 := IdentifyScheme(req)
- assert.Equal(t, "https", scheme3)
+ assert.Equal(t, "https", Scheme(req))
req.Header.Set(HeaderXForwardedProto, "http")
- scheme4 := IdentifyScheme(req)
- assert.Equal(t, "http", scheme4)
+ assert.Equal(t, "http", Scheme(req))
}
func TestRequestSaveFile(t *testing.T) {
@@ -298,7 +303,7 @@ func TestRequestSaveFileForExistingFile(t *testing.T) {
size, err := saveFile(&buf, "testdata/file1.txt")
assert.NotNil(t, err)
- assert.Equal(t, "ahttp: open testdata/file1.txt: file exists", err.Error())
+ assert.True(t, strings.HasPrefix(err.Error(), "ahttp: open testdata/file1.txt:"))
assert.Equal(t, int64(0), size)
}
From fbcb631d7a34a9865c0d313233113639bfa6298b Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Thu, 24 May 2018 21:02:15 -0700
Subject: [PATCH 09/13] gzip writer update
---
gzip_response.go | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/gzip_response.go b/gzip_response.go
index ab6c02b..7686c6b 100644
--- a/gzip_response.go
+++ b/gzip_response.go
@@ -11,8 +11,6 @@ import (
"net"
"net/http"
"sync"
-
- "aahframework.org/essentials.v0"
)
var (
@@ -95,8 +93,9 @@ func (g *GzipResponse) BytesWritten() int {
// Close method closes the writer if possible.
func (g *GzipResponse) Close() error {
- ess.CloseQuietly(g.gw)
- g.gw = nil
+ if err := g.gw.Close(); err != nil {
+ return err
+ }
return g.r.Close()
}
@@ -138,9 +137,9 @@ func (g *GzipResponse) Push(target string, opts *http.PushOptions) error {
// releaseGzipResponse method resets and puts the gzip response into pool.
func releaseGzipResponse(gw *GzipResponse) {
- releaseGzipWriter(gw.gw)
- releaseResponse(gw.r)
_ = gw.Close()
+ gwPool.Put(gw.gw)
+ releaseResponse(gw.r)
grPool.Put(gw)
}
@@ -156,8 +155,3 @@ func acquireGzipWriter(w io.Writer) *gzip.Writer {
ngw.Reset(w)
return ngw
}
-
-func releaseGzipWriter(gw *gzip.Writer) {
- _ = gw.Close()
- gwPool.Put(gw)
-}
From 052312c6da17ba8df5b83fb7a6cf1d1f10980901 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Sun, 27 May 2018 14:13:53 -0700
Subject: [PATCH 10/13] go-aah/aah#186 deprecated cleanup in ahttp lib
---
gzip_response.go | 22 ---------------------
gzip_response_test.go | 6 +++---
request.go | 46 ++++---------------------------------------
request_test.go | 45 +-----------------------------------------
response.go | 20 -------------------
response_test.go | 12 +++++------
6 files changed, 14 insertions(+), 137 deletions(-)
diff --git a/gzip_response.go b/gzip_response.go
index 7686c6b..b73d1a3 100644
--- a/gzip_response.go
+++ b/gzip_response.go
@@ -29,28 +29,6 @@ var (
_ ResponseWriter = (*GzipResponse)(nil)
)
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
-// Package methods
-//___________________________________
-
-// TODO for old method cleanup
-
-// GetGzipResponseWriter wraps `http.ResponseWriter`, returns aah framework response
-// writer that allows to advantage of response process.
-// Deprecated use `WrapGzipWriter` instead.
-func GetGzipResponseWriter(w ResponseWriter) ResponseWriter {
- gr := grPool.Get().(*GzipResponse)
- gr.gw = acquireGzipWriter(w)
- gr.r = w.(*Response)
- return gr
-}
-
-// PutGzipResponseWiriter method resets and puts the gzip writer into pool.
-// Deprecated use `ReleaseResponseWriter` instead.
-func PutGzipResponseWiriter(rw ResponseWriter) {
- releaseGzipResponse(rw.(*GzipResponse))
-}
-
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// GzipResponse
//___________________________________
diff --git a/gzip_response_test.go b/gzip_response_test.go
index fc1df37..c6b4ff1 100644
--- a/gzip_response_test.go
+++ b/gzip_response_test.go
@@ -21,8 +21,8 @@ import (
func TestHTTPGzipWriter(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
GzipLevel = gzip.BestSpeed
- gw := GetGzipResponseWriter(GetResponseWriter(w))
- defer PutGzipResponseWiriter(gw)
+ gw := WrapGzipWriter(AcquireResponseWriter(w))
+ defer ReleaseResponseWriter(gw)
gw.Header().Set(HeaderVary, HeaderAcceptEncoding)
gw.Header().Set(HeaderContentEncoding, "gzip")
@@ -92,7 +92,7 @@ func TestHTTPGzipHijack(t *testing.T) {
ngw, _ := gzip.NewWriterLevel(w, GzipLevel)
gwPool.Put(ngw)
}
- gw := WrapGzipWriter(GetResponseWriter(w))
+ gw := WrapGzipWriter(AcquireResponseWriter(w))
con, rw, err := gw.(http.Hijacker).Hijack()
assert.FailOnError(t, err, "")
diff --git a/request.go b/request.go
index dc0e68f..e5ab343 100644
--- a/request.go
+++ b/request.go
@@ -12,7 +12,6 @@ import (
"net/http"
"net/url"
"os"
- "path/filepath"
"strings"
"sync"
@@ -43,8 +42,7 @@ func ParseRequest(r *http.Request, req *Request) *Request {
req.Referer = getReferer(r.Header)
req.UserAgent = r.Header.Get(HeaderUserAgent)
req.IsGzipAccepted = strings.Contains(r.Header.Get(HeaderAcceptEncoding), "gzip")
- req.Raw = r
-
+ req.raw = r
return req
}
@@ -89,12 +87,7 @@ type Request struct {
// otherwise false.
IsGzipAccepted bool
- // Raw an object of Go HTTP server, direct interaction with
- // raw object is not encouraged.
- //
- // DEPRECATED: Raw field to be unexported on v1 release, use `Req.Unwarp()` instead.
- Raw *http.Request
-
+ raw *http.Request
locale *Locale
contentType *ContentType
acceptContentType *ContentType
@@ -252,7 +245,7 @@ func (r *Request) Body() io.ReadCloser {
// Unwrap method returns the underlying *http.Request instance of Go HTTP server,
// direct interaction with raw object is not encouraged. Use it appropriately.
func (r *Request) Unwrap() *http.Request {
- return r.Raw
+ return r.raw
}
// SaveFile method saves an uploaded multipart file for given key from the HTTP
@@ -275,37 +268,6 @@ func (r *Request) SaveFile(key, dstFile string) (int64, error) {
return saveFile(uploadedFile, dstFile)
}
-// SaveFiles method saves an uploaded multipart file(s) for the given key
-// from the HTTP request into given destination directory. It uses the filename
-// as uploaded filename from the request
-func (r *Request) SaveFiles(key, dstPath string) ([]int64, []error) {
- if !ess.IsDir(dstPath) {
- return []int64{0}, []error{fmt.Errorf("ahttp: destination path, '%s' is not a directory", dstPath)}
- }
-
- if ess.IsStrEmpty(key) {
- return []int64{0}, []error{fmt.Errorf("ahttp: form file key, '%s' is empty", key)}
- }
-
- var errs []error
- var sizes []int64
- for _, file := range r.Params.File[key] {
- uploadedFile, err := file.Open()
- if err != nil {
- sizes = append(sizes, 0)
- errs = append(errs, err)
- continue
- }
-
- if size, err := saveFile(uploadedFile, filepath.Join(dstPath, file.Filename)); err != nil {
- sizes = append(sizes, size)
- errs = append(errs, err)
- }
- ess.CloseQuietly(uploadedFile)
- }
- return sizes, errs
-}
-
// Reset method resets request instance for reuse.
func (r *Request) Reset() {
r.Scheme = ""
@@ -318,8 +280,8 @@ func (r *Request) Reset() {
r.Referer = ""
r.UserAgent = ""
r.IsGzipAccepted = false
- r.Raw = nil
+ r.raw = nil
r.locale = nil
r.contentType = nil
r.acceptContentType = nil
diff --git a/request_test.go b/request_test.go
index 3439571..9a4df11 100644
--- a/request_test.go
+++ b/request_test.go
@@ -105,7 +105,7 @@ func TestHTTPParseRequest(t *testing.T) {
ReleaseRequest(aahReq)
assert.Nil(t, aahReq.Header)
assert.Nil(t, aahReq.Params)
- assert.Nil(t, aahReq.Raw)
+ assert.Nil(t, aahReq.raw)
assert.True(t, aahReq.UserAgent == "")
}
@@ -255,49 +255,6 @@ func TestRequestSaveFileCannotCreateFile(t *testing.T) {
assert.True(t, strings.HasPrefix(err.Error(), "ahttp: open /root/aah.txt"))
}
-func TestRequestSaveFiles(t *testing.T) {
- aahReq, dir, teardown := setUpRequestSaveFiles(t)
- defer teardown()
-
- sizes, errs := aahReq.SaveFiles("framework", dir)
- assert.Nil(t, errs)
- assert.Nil(t, sizes)
- _, err := os.Stat(dir + "/aah")
- assert.Nil(t, err)
- _, err = os.Stat(dir + "/aah2")
- assert.Nil(t, err)
-}
-
-func TestRequestSaveFilesFailsVaildation(t *testing.T) {
- aahReq, dir, teardown := setUpRequestSaveFiles(t)
- defer teardown()
-
- // Empty key
- sizes, errs := aahReq.SaveFiles("", dir)
- assert.NotNil(t, errs)
- assert.Equal(t, "ahttp: form file key, '' is empty", errs[0].Error())
- assert.Equal(t, int64(0), sizes[0])
-
- // Empty directory
- sizes, errs = aahReq.SaveFiles("key", "")
- assert.NotNil(t, errs)
- assert.Equal(t, "ahttp: destination path, '' is not a directory", errs[0].Error())
- assert.Equal(t, int64(0), sizes[0])
-}
-
-func TestRequestSaveFilesCannotCreateFile(t *testing.T) {
- aahReq, _, teardown := setUpRequestSaveFiles(t)
- defer teardown()
-
- sizes, errs := aahReq.SaveFiles("framework", "/root")
- assert.NotNil(t, errs)
- assert.Equal(t, int64(0), sizes[0])
-
- errMsg := errs[0].Error()
- assert.True(t, ("ahttp: open /root/aah: permission denied" == errMsg ||
- "ahttp: destination path, '/root' is not a directory" == errMsg))
-}
-
func TestRequestSaveFileForExistingFile(t *testing.T) {
var buf bytes.Buffer
diff --git a/response.go b/response.go
index 5992088..6bee1ec 100644
--- a/response.go
+++ b/response.go
@@ -40,26 +40,6 @@ type ResponseWriter interface {
Unwrap() http.ResponseWriter
}
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
-// Package methods
-//___________________________________
-
-// TODO for old method cleanup
-
-// GetResponseWriter method wraps given writer and returns the aah response writer.
-// Deprecated use `AcquireResponseWriter` instead.
-func GetResponseWriter(w http.ResponseWriter) ResponseWriter {
- rw := responsePool.Get().(*Response)
- rw.w = w
- return rw
-}
-
-// PutResponseWriter method puts response writer back to pool.
-// Deprecated use `ReleaseResponseWriter` instead.
-func PutResponseWriter(aw ResponseWriter) {
- releaseResponse(aw.(*Response))
-}
-
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Response
//___________________________________
diff --git a/response_test.go b/response_test.go
index 4bdbcb0..d3a32f3 100644
--- a/response_test.go
+++ b/response_test.go
@@ -18,8 +18,8 @@ import (
func TestHTTPResponseWriter(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
- writer := GetResponseWriter(w)
- defer PutResponseWriter(writer)
+ writer := AcquireResponseWriter(w)
+ defer ReleaseResponseWriter(writer)
writer.WriteHeader(http.StatusOK)
assert.Equal(t, http.StatusOK, writer.Status())
@@ -47,8 +47,8 @@ func TestHTTPNoStatusWritten(t *testing.T) {
func TestHTTPMultipleStatusWritten(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
- writer := GetResponseWriter(w)
- defer PutResponseWriter(writer)
+ writer := AcquireResponseWriter(w)
+ defer ReleaseResponseWriter(writer)
writer.WriteHeader(http.StatusOK)
writer.WriteHeader(http.StatusAccepted)
@@ -85,8 +85,8 @@ func TestHTTPHijackCall(t *testing.T) {
func TestHTTPCallCloseNotifyAndFlush(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
- writer := GetResponseWriter(w)
- defer PutResponseWriter(writer)
+ writer := AcquireResponseWriter(w)
+ defer ReleaseResponseWriter(writer)
_, _ = writer.Write([]byte("aah framework calling close notify and flush"))
assert.Equal(t, 44, writer.BytesWritten())
From 995fd2f81484a723d8df2512ee52502478852924 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Wed, 6 Jun 2018 01:01:59 -0700
Subject: [PATCH 11/13] request parameter improvements
---
ahttp.go | 5 +++
request.go | 108 ++++++++++--------------------------------------
request_test.go | 83 ++++++++++++-------------------------
3 files changed, 53 insertions(+), 143 deletions(-)
diff --git a/ahttp.go b/ahttp.go
index 1684adc..261d308 100644
--- a/ahttp.go
+++ b/ahttp.go
@@ -98,10 +98,15 @@ func WrapGzipWriter(w io.Writer) ResponseWriter {
// Scheme method is to identify value of protocol value. It's is derived
// one, Go language doesn't provide directly.
+//
// - `X-Forwarded-Proto` is not empty, returns as-is
+//
// - `X-Forwarded-Protocol` is not empty, returns as-is
+//
// - `http.Request.TLS` is not nil or `X-Forwarded-Ssl == on` returns `https`
+//
// - `X-Url-Scheme` is not empty, returns as-is
+//
// - returns `http`
func Scheme(r *http.Request) string {
if scheme := r.Header.Get(HeaderXForwardedProto); scheme != "" {
diff --git a/request.go b/request.go
index e5ab343..57bf20c 100644
--- a/request.go
+++ b/request.go
@@ -38,7 +38,6 @@ func ParseRequest(r *http.Request, req *Request) *Request {
req.Method = r.Method
req.Path = r.URL.Path
req.Header = r.Header
- req.Params = &Params{Query: r.URL.Query()}
req.Referer = getReferer(r.Header)
req.UserAgent = r.Header.Get(HeaderUserAgent)
req.IsGzipAccepted = strings.Contains(r.Header.Get(HeaderAcceptEncoding), "gzip")
@@ -53,16 +52,13 @@ func ParseRequest(r *http.Request, req *Request) *Request {
// Request type extends `http.Request` and provides multiple helper methods
// per industry RFC guideline for aah framework.
type Request struct {
- // Scheme value is protocol; it's a derived value in the order as below.
- // - `X-Forwarded-Proto` is not empty return value as is
- // - `http.Request.TLS` is not nil value is `https`
- // - `http.Request.TLS` is nil value is `http`
+ // Scheme value is protocol, refer to method `ahttp.Scheme`.
Scheme string
- // Host value of the HTTP 'Host' header (e.g. 'example.com:8080').
+ // Host value is HTTP 'Host' header (e.g. 'example.com:8080').
Host string
- // Proto value of the current HTTP request protocol. (e.g. HTTP/1.1, HTTP/2.0)
+ // Proto value is current HTTP request protocol. (e.g. HTTP/1.1, HTTP/2.0)
Proto string
// Method request method e.g. `GET`, `POST`, etc.
@@ -74,13 +70,13 @@ type Request struct {
// Header request HTTP headers
Header http.Header
- // Params contains values from Path, Query, Form and File.
- Params *Params
+ // PathParams value is URL path parameters.
+ PathParams PathParams
- // Referer value of the HTTP 'Referrer' (or 'Referer') header.
+ // Referer value is HTTP 'Referrer' (or 'Referer') header.
Referer string
- // UserAgent value of the HTTP 'User-Agent' header.
+ // UserAgent value is HTTP 'User-Agent' header.
UserAgent string
// IsGzipAccepted is true if the HTTP client accepts Gzip response,
@@ -205,36 +201,44 @@ func (r *Request) URL() *url.URL {
// PathValue method returns value for given Path param key otherwise empty string.
// For eg.: /users/:userId => PathValue("userId")
func (r *Request) PathValue(key string) string {
- return r.Params.PathValue(key)
+ return r.PathParams.Get(key)
}
// QueryValue method returns value for given URL query param key
// otherwise empty string.
func (r *Request) QueryValue(key string) string {
- return r.Params.QueryValue(key)
+ return r.URL().Query().Get(key)
}
// QueryArrayValue method returns array value for given URL query param key
// otherwise empty string slice.
func (r *Request) QueryArrayValue(key string) []string {
- return r.Params.QueryArrayValue(key)
+ if values, found := r.URL().Query()[key]; found {
+ return values
+ }
+ return []string{}
}
// FormValue method returns value for given form key otherwise empty string.
func (r *Request) FormValue(key string) string {
- return r.Params.FormValue(key)
+ return r.Unwrap().FormValue(key)
}
// FormArrayValue method returns array value for given form key
// otherwise empty string slice.
func (r *Request) FormArrayValue(key string) []string {
- return r.Params.FormArrayValue(key)
+ if r.Unwrap().Form != nil {
+ if values, found := r.Unwrap().Form[key]; found {
+ return values
+ }
+ }
+ return []string{}
}
// FormFile method returns the first file for the provided form key otherwise
// returns error. It is caller responsibility to close the file.
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
- return r.Params.FormFile(key)
+ return r.Unwrap().FormFile(key)
}
// Body method returns the HTTP request body.
@@ -276,7 +280,7 @@ func (r *Request) Reset() {
r.Method = ""
r.Path = ""
r.Header = nil
- r.Params = nil
+ r.PathParams = nil
r.Referer = ""
r.UserAgent = ""
r.IsGzipAccepted = false
@@ -314,74 +318,6 @@ func (p PathParams) Len() int {
return len(p)
}
-//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
-// Params
-//___________________________________
-
-// Params structure holds value of Path, Query, Form and File.
-type Params struct {
- Path PathParams
- Query url.Values
- Form url.Values
- File map[string][]*multipart.FileHeader
-}
-
-// PathValue method returns value for given Path param key otherwise empty string.
-// For eg.: `/users/:userId` => `PathValue("userId")`.
-func (p *Params) PathValue(key string) string {
- if p.Path != nil {
- return p.Path.Get(key)
- }
- return ""
-}
-
-// QueryValue method returns value for given URL query param key
-// otherwise empty string.
-func (p *Params) QueryValue(key string) string {
- return p.Query.Get(key)
-}
-
-// QueryArrayValue method returns array value for given URL query param key
-// otherwise empty string slice.
-func (p *Params) QueryArrayValue(key string) []string {
- if values, found := p.Query[key]; found {
- return values
- }
- return []string{}
-}
-
-// FormValue method returns value for given form key otherwise empty string.
-func (p *Params) FormValue(key string) string {
- if p.Form != nil {
- return p.Form.Get(key)
- }
- return ""
-}
-
-// FormArrayValue method returns array value for given form key
-// otherwise empty string slice.
-func (p *Params) FormArrayValue(key string) []string {
- if p.Form != nil {
- if values, found := p.Form[key]; found {
- return values
- }
- }
- return []string{}
-}
-
-// FormFile method returns the first file for the provided form key
-// otherwise returns error. It is caller responsibility to close the file.
-func (p *Params) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
- if p.File != nil {
- if fh := p.File[key]; len(fh) > 0 {
- f, err := fh[0].Open()
- return f, fh[0], err
- }
- return nil, nil, fmt.Errorf("ahttp: no such key/file: %s", key)
- }
- return nil, nil, nil
-}
-
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Unexported methods
//___________________________________
diff --git a/request_test.go b/request_test.go
index 9a4df11..1e7f408 100644
--- a/request_test.go
+++ b/request_test.go
@@ -7,6 +7,7 @@ package ahttp
import (
"bytes"
"crypto/tls"
+ "errors"
"mime/multipart"
"net/http"
"net/http/httptest"
@@ -88,7 +89,7 @@ func TestHTTPParseRequest(t *testing.T) {
f, hdr, err := aahReq.FormFile("no_file")
assert.Nil(t, f)
assert.Nil(t, hdr)
- assert.Nil(t, err)
+ assert.NotNil(t, err) // request Content-Type isn't multipart/form-data
assert.False(t, aahReq.IsJSONP())
assert.False(t, aahReq.IsAJAX())
@@ -104,7 +105,6 @@ func TestHTTPParseRequest(t *testing.T) {
// Release it
ReleaseRequest(aahReq)
assert.Nil(t, aahReq.Header)
- assert.Nil(t, aahReq.Params)
assert.Nil(t, aahReq.raw)
assert.True(t, aahReq.UserAgent == "")
}
@@ -115,16 +115,17 @@ func TestHTTPRequestParams(t *testing.T) {
req1.Method = MethodPost
req1.URL, _ = url.Parse("http://localhost:8080/welcome1.html?_ref=true&names=Test1&names=Test%202")
- params1 := AcquireRequest(req1).Params
- params1.Path = make(map[string]string)
- params1.Path["userId"] = "100001"
- assert.Equal(t, "true", params1.QueryValue("_ref"))
- assert.Equal(t, "Test1", params1.QueryArrayValue("names")[0])
- assert.Equal(t, "Test 2", params1.QueryArrayValue("names")[1])
- assert.True(t, len(params1.QueryArrayValue("not-exists")) == 0)
- assert.Equal(t, "100001", params1.PathValue("userId"))
- assert.Equal(t, "", params1.PathValue("accountId"))
- assert.Equal(t, 1, params1.Path.Len())
+ aahReq1 := AcquireRequest(req1)
+ aahReq1.PathParams = PathParams{}
+ aahReq1.PathParams["userId"] = "100001"
+
+ assert.Equal(t, "true", aahReq1.QueryValue("_ref"))
+ assert.Equal(t, "Test1", aahReq1.QueryArrayValue("names")[0])
+ assert.Equal(t, "Test 2", aahReq1.QueryArrayValue("names")[1])
+ assert.True(t, len(aahReq1.QueryArrayValue("not-exists")) == 0)
+ assert.Equal(t, "100001", aahReq1.PathValue("userId"))
+ assert.Equal(t, "", aahReq1.PathValue("accountId"))
+ assert.Equal(t, 1, aahReq1.PathParams.Len())
// Form value
form := url.Values{}
@@ -137,24 +138,22 @@ func TestHTTPRequestParams(t *testing.T) {
_ = req2.ParseForm()
aahReq2 := AcquireRequest(req2)
- aahReq2.Params.Form = req2.Form
-
- params2 := aahReq2.Params
assert.NotNil(t, aahReq2.Body())
- assert.Equal(t, "welcome", params2.FormValue("username"))
- assert.Equal(t, "welcome@welcome.com", params2.FormValue("email"))
- assert.Equal(t, "Test1", params2.FormArrayValue("names")[0])
- assert.Equal(t, "Test 2 value", params2.FormArrayValue("names")[1])
- assert.True(t, len(params2.FormArrayValue("not-exists")) == 0)
- assert.Equal(t, 0, params2.Path.Len())
+ assert.Equal(t, "welcome", aahReq2.FormValue("username"))
+ assert.Equal(t, "welcome@welcome.com", aahReq2.FormValue("email"))
+ assert.Equal(t, "Test1", aahReq2.FormArrayValue("names")[0])
+ assert.Equal(t, "Test 2 value", aahReq2.FormArrayValue("names")[1])
+ assert.True(t, len(aahReq2.FormArrayValue("not-exists")) == 0)
+ assert.Equal(t, 0, aahReq2.PathParams.Len())
ReleaseRequest(aahReq2)
// File value
- req3, _ := http.NewRequest("POST", "http://localhost:8080/user/registration", nil)
+ req3, _ := http.NewRequest("POST", "http://localhost:8080/user/registration", strings.NewReader(form.Encode()))
req3.Header.Add(HeaderContentType, ContentTypeMultipartForm.String())
aahReq3 := AcquireRequest(req3)
- aahReq3.Params.File = make(map[string][]*multipart.FileHeader)
- aahReq3.Params.File["testfile.txt"] = []*multipart.FileHeader{{Filename: "testfile.txt"}}
+ aahReq3.Unwrap().MultipartForm = new(multipart.Form)
+ aahReq3.Unwrap().MultipartForm.File = make(map[string][]*multipart.FileHeader)
+ aahReq3.Unwrap().MultipartForm.File["testfile.txt"] = []*multipart.FileHeader{{Filename: "testfile.txt"}}
f, fh, err := aahReq3.FormFile("testfile.txt")
assert.Nil(t, f)
assert.Equal(t, "testfile.txt", fh.Filename)
@@ -243,7 +242,7 @@ func TestRequestSaveFileFailsForNotFoundFile(t *testing.T) {
_, err := aahReq.SaveFile("unknown-key", path)
assert.NotNil(t, err)
- assert.Equal(t, "ahttp: no such key/file: unknown-key", err.Error())
+ assert.Equal(t, errors.New("http: no such file"), err)
}
func TestRequestSaveFileCannotCreateFile(t *testing.T) {
@@ -284,12 +283,12 @@ func setUpRequestSaveFile(t *testing.T) (*Request, string, func()) {
req, _ := http.NewRequest("POST", "http://localhost:8080", buf)
req.Header.Add(HeaderContentType, multipartWriter.FormDataContentType())
aahReq := AcquireRequest(req)
- aahReq.Params.File = make(map[string][]*multipart.FileHeader)
_, header, err := req.FormFile("framework")
assert.Nil(t, err)
- aahReq.Params.File["framework"] = []*multipart.FileHeader{header}
+ aahReq.Unwrap().MultipartForm.File = make(map[string][]*multipart.FileHeader)
+ aahReq.Unwrap().MultipartForm.File["framework"] = []*multipart.FileHeader{header}
path := "testdata/aah.txt"
@@ -297,33 +296,3 @@ func setUpRequestSaveFile(t *testing.T) (*Request, string, func()) {
_ = os.Remove(path) //Teardown
}
}
-
-func setUpRequestSaveFiles(t *testing.T) (*Request, string, func()) {
- buf := new(bytes.Buffer)
- multipartWriter := multipart.NewWriter(buf)
- _, err := multipartWriter.CreateFormFile("framework", "aah")
- assert.Nil(t, err)
- _, err = multipartWriter.CreateFormFile("framework2", "aah2")
- assert.Nil(t, err)
-
- ess.CloseQuietly(multipartWriter)
-
- req, _ := http.NewRequest("POST", "http://localhost:8080", buf)
- req.Header.Add(HeaderContentType, multipartWriter.FormDataContentType())
- aahReq := AcquireRequest(req)
- aahReq.Params.File = make(map[string][]*multipart.FileHeader)
-
- _, header, err := req.FormFile("framework")
- assert.Nil(t, err)
- _, header2, err := req.FormFile("framework2")
- assert.Nil(t, err)
-
- aahReq.Params.File["framework"] = []*multipart.FileHeader{header, header2}
-
- dir := "testdata/upload"
-
- _ = ess.MkDirAll(dir, 0755)
- return aahReq, dir, func() {
- _ = os.RemoveAll(dir)
- }
-}
From a4d8ba9be12e1587b9a101e08e53ab4402f43d19 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Sat, 30 Jun 2018 00:41:24 -0700
Subject: [PATCH 12/13] godoc update
---
ahttp.go | 7 ++++---
request.go | 14 +++++---------
2 files changed, 9 insertions(+), 12 deletions(-)
diff --git a/ahttp.go b/ahttp.go
index 261d308..90a3589 100644
--- a/ahttp.go
+++ b/ahttp.go
@@ -1,5 +1,5 @@
// Copyright (c) Jeevanandam M (https://github.com/jeevatkm)
-// go-aah/ahttp source code and usage is governed by a MIT style
+// aahframework.org/ahttp source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
// Package ahttp is to cater HTTP helper methods for aah framework.
@@ -128,7 +128,7 @@ func Scheme(r *http.Request) string {
return "http"
}
-// Host method is to correct Hosyt source value from HTTP request.
+// Host method is to correct Host value from HTTP request.
func Host(r *http.Request) string {
if r.URL.Host == "" {
return r.Host
@@ -137,7 +137,8 @@ func Host(r *http.Request) string {
}
// ClientIP method returns remote Client IP address aka Remote IP.
-// It parses in the order of given set of headers otherwise it uses default
+//
+// It parses in the order of given headers otherwise it uses default
// default header set `X-Forwarded-For`, `X-Real-IP`, "X-Appengine-Remote-Addr"
// and finally `http.Request.RemoteAddr`.
func ClientIP(r *http.Request, hdrs ...string) string {
diff --git a/request.go b/request.go
index 57bf20c..0039f41 100644
--- a/request.go
+++ b/request.go
@@ -1,5 +1,5 @@
// Copyright (c) Jeevanandam M (https://github.com/jeevatkm)
-// go-aah/ahttp source code and usage is governed by a MIT style
+// aahframework.org/ahttp source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package ahttp
@@ -61,13 +61,13 @@ type Request struct {
// Proto value is current HTTP request protocol. (e.g. HTTP/1.1, HTTP/2.0)
Proto string
- // Method request method e.g. `GET`, `POST`, etc.
+ // Method value is HTTP verb from request e.g. `GET`, `POST`, etc.
Method string
- // Path the request URL Path e.g. `/app/login.html`.
+ // Path value is request relative URL Path e.g. `/app/login.html`.
Path string
- // Header request HTTP headers
+ // Header is request HTTP headers
Header http.Header
// PathParams value is URL path parameters.
@@ -132,12 +132,8 @@ func (r *Request) SetAcceptEncoding(encoding *AcceptSpec) *Request {
}
// ClientIP method returns remote client IP address aka Remote IP.
-// It parses in the order of `X-Forwarded-For`, `X-Real-IP` and
-// finally `http.Request.RemoteAddr`.
//
-// Note: Make these header configurable from aah.conf so that aah user
-// could configure headers of their choice. For e.g.
-// X-Appengine-Remote-Addr, etc.
+// Refer to method `ahttp.ClientIP`.
func (r *Request) ClientIP() string {
return ClientIP(r.Unwrap())
}
From 85d0444ef8f54793248d39cbdcbe372899ae1043 Mon Sep 17 00:00:00 2001
From: Jeevanandam M
Date: Fri, 6 Jul 2018 21:56:01 -0700
Subject: [PATCH 13/13] readme and version bump for v0.11.0 release
---
README.md | 22 +++++++++++++---------
version.go | 4 ++--
2 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index 6f07137..77aca78 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,21 @@
-# ahttp - aah framework
-[![Build Status](https://travis-ci.org/go-aah/ahttp.svg?branch=master)](https://travis-ci.org/go-aah/ahttp) [![codecov](https://codecov.io/gh/go-aah/ahttp/branch/master/graph/badge.svg)](https://codecov.io/gh/go-aah/ahttp/branch/master) [![Go Report Card](https://goreportcard.com/badge/aahframework.org/ahttp.v0-unstable)](https://goreportcard.com/report/aahframework.org/ahttp.v0-unstable) [![Version](https://img.shields.io/badge/version-0.10-blue.svg)](https://github.com/go-aah/ahttp/releases/latest) [![GoDoc](https://godoc.org/aahframework.org/ahttp.v0-unstable?status.svg)](https://godoc.org/aahframework.org/ahttp.v0-unstable) [![License](https://img.shields.io/github/license/go-aah/ahttp.svg)](LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@aahframework-55acee.svg)](https://twitter.com/aahframework)
+
+
+
HTTP extension library by aah framework
+
+
+
+
-***v0.10 [released](https://github.com/go-aah/ahttp/releases/latest) and tagged on Sep 01, 2017***
+HTTP extension Library is used to handle/process Request and Response (headers, body, gzip, etc).
-HTTP Library built to process, manipulate Request and Response (headers, body, gzip, etc).
+### News
-*`ahttp` developed for aah framework. However, it's an independent library, can be used separately with any `Go` language project. Feel free to use it.*
+ * `v0.11.0` [released](https://github.com/go-aah/ahttp/releases/latest) and tagged on Jul 06, 2018.
+
+## Installation
-# Installation
-#### Stable Version - Production Ready
```bash
-# install the library
go get -u aahframework.org/ahttp.v0
```
-Visit official website https://aahframework.org to learn more.
+Visit official website https://aahframework.org to learn more about `aah` framework.
diff --git a/version.go b/version.go
index dca6a6f..aaa8b26 100644
--- a/version.go
+++ b/version.go
@@ -1,8 +1,8 @@
// Copyright (c) Jeevanandam M (https://github.com/jeevatkm)
-// go-aah/ahttp source code and usage is governed by a MIT style
+// aahframework.org/ahttp source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package ahttp
// Version no. of aah framework ahttp library
-const Version = "0.11.0-edge"
+const Version = "0.11.0"