diff options
author | Frédéric Guillot <fred@miniflux.net> | 2018-04-29 16:35:04 -0700 |
---|---|---|
committer | Frédéric Guillot <fred@miniflux.net> | 2018-04-29 16:35:04 -0700 |
commit | f49b42f70f902d4da1e0fa4080e99164b331b716 (patch) | |
tree | c6bdd19f11d100c44b0d30344ec37038f649e988 /http | |
parent | 1eba1730d1af50ed545f4fde78b22d6fb62ca11e (diff) |
Use vanilla HTTP handlers (refactoring)
Diffstat (limited to 'http')
-rw-r--r-- | http/context/context.go | 108 | ||||
-rw-r--r-- | http/handler/context.go | 167 | ||||
-rw-r--r-- | http/handler/handler.go | 54 | ||||
-rw-r--r-- | http/handler/html_response.go | 65 | ||||
-rw-r--r-- | http/handler/json_response.go | 111 | ||||
-rw-r--r-- | http/handler/request.go | 124 | ||||
-rw-r--r-- | http/handler/response.go | 88 | ||||
-rw-r--r-- | http/handler/xml_response.go | 29 | ||||
-rw-r--r-- | http/request/request.go | 90 | ||||
-rw-r--r-- | http/response/html/html.go | 57 | ||||
-rw-r--r-- | http/response/json/json.go | 107 | ||||
-rw-r--r-- | http/response/response.go | 34 | ||||
-rw-r--r-- | http/response/xml/xml.go | 23 |
13 files changed, 419 insertions, 638 deletions
diff --git a/http/context/context.go b/http/context/context.go new file mode 100644 index 0000000..cf7cb09 --- /dev/null +++ b/http/context/context.go @@ -0,0 +1,108 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package context + +import ( + "net/http" + + "github.com/miniflux/miniflux/middleware" +) + +// Context contains helper functions related to the current request. +type Context struct { + request *http.Request +} + +// IsAdminUser checks if the logged user is administrator. +func (c *Context) IsAdminUser() bool { + return c.getContextBoolValue(middleware.IsAdminUserContextKey) +} + +// IsAuthenticated returns a boolean if the user is authenticated. +func (c *Context) IsAuthenticated() bool { + return c.getContextBoolValue(middleware.IsAuthenticatedContextKey) +} + +// UserID returns the UserID of the logged user. +func (c *Context) UserID() int64 { + return c.getContextIntValue(middleware.UserIDContextKey) +} + +// UserTimezone returns the timezone used by the logged user. +func (c *Context) UserTimezone() string { + value := c.getContextStringValue(middleware.UserTimezoneContextKey) + if value == "" { + value = "UTC" + } + return value +} + +// UserLanguage get the locale used by the current logged user. +func (c *Context) UserLanguage() string { + language := c.getContextStringValue(middleware.UserLanguageContextKey) + if language == "" { + language = "en_US" + } + return language +} + +// CSRF returns the current CSRF token. +func (c *Context) CSRF() string { + return c.getContextStringValue(middleware.CSRFContextKey) +} + +// SessionID returns the current session ID. +func (c *Context) SessionID() string { + return c.getContextStringValue(middleware.SessionIDContextKey) +} + +// UserSessionToken returns the current user session token. +func (c *Context) UserSessionToken() string { + return c.getContextStringValue(middleware.UserSessionTokenContextKey) +} + +// OAuth2State returns the current OAuth2 state. +func (c *Context) OAuth2State() string { + return c.getContextStringValue(middleware.OAuth2StateContextKey) +} + +// FlashMessage returns the message message if any. +func (c *Context) FlashMessage() string { + return c.getContextStringValue(middleware.FlashMessageContextKey) +} + +// FlashErrorMessage returns the message error message if any. +func (c *Context) FlashErrorMessage() string { + return c.getContextStringValue(middleware.FlashErrorMessageContextKey) +} + +func (c *Context) getContextStringValue(key *middleware.ContextKey) string { + if v := c.request.Context().Value(key); v != nil { + return v.(string) + } + + return "" +} + +func (c *Context) getContextBoolValue(key *middleware.ContextKey) bool { + if v := c.request.Context().Value(key); v != nil { + return v.(bool) + } + + return false +} + +func (c *Context) getContextIntValue(key *middleware.ContextKey) int64 { + if v := c.request.Context().Value(key); v != nil { + return v.(int64) + } + + return 0 +} + +// New creates a new Context. +func New(r *http.Request) *Context { + return &Context{r} +} diff --git a/http/handler/context.go b/http/handler/context.go deleted file mode 100644 index 2d70271..0000000 --- a/http/handler/context.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package handler - -import ( - "net/http" - - "github.com/miniflux/miniflux/crypto" - "github.com/miniflux/miniflux/http/route" - "github.com/miniflux/miniflux/locale" - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/middleware" - "github.com/miniflux/miniflux/model" - "github.com/miniflux/miniflux/storage" - - "github.com/gorilla/mux" -) - -// Context contains helper functions related to the current request. -type Context struct { - writer http.ResponseWriter - request *http.Request - store *storage.Storage - router *mux.Router - user *model.User - translator *locale.Translator -} - -// IsAdminUser checks if the logged user is administrator. -func (c *Context) IsAdminUser() bool { - if v := c.request.Context().Value(middleware.IsAdminUserContextKey); v != nil { - return v.(bool) - } - return false -} - -// UserTimezone returns the timezone used by the logged user. -func (c *Context) UserTimezone() string { - value := c.getContextStringValue(middleware.UserTimezoneContextKey) - if value == "" { - value = "UTC" - } - return value -} - -// IsAuthenticated returns a boolean if the user is authenticated. -func (c *Context) IsAuthenticated() bool { - if v := c.request.Context().Value(middleware.IsAuthenticatedContextKey); v != nil { - return v.(bool) - } - return false -} - -// UserID returns the UserID of the logged user. -func (c *Context) UserID() int64 { - if v := c.request.Context().Value(middleware.UserIDContextKey); v != nil { - return v.(int64) - } - return 0 -} - -// LoggedUser returns all properties related to the logged user. -func (c *Context) LoggedUser() *model.User { - if c.user == nil { - var err error - c.user, err = c.store.UserByID(c.UserID()) - if err != nil { - logger.Fatal("[Context] %v", err) - } - - if c.user == nil { - logger.Fatal("Unable to find user from context") - } - } - - return c.user -} - -// UserLanguage get the locale used by the current logged user. -func (c *Context) UserLanguage() string { - if c.IsAuthenticated() { - user := c.LoggedUser() - return user.Language - } - - language := c.getContextStringValue(middleware.UserLanguageContextKey) - if language == "" { - language = "en_US" - } - return language -} - -// Translate translates a message in the current language. -func (c *Context) Translate(message string, args ...interface{}) string { - return c.translator.GetLanguage(c.UserLanguage()).Get(message, args...) -} - -// CSRF returns the current CSRF token. -func (c *Context) CSRF() string { - return c.getContextStringValue(middleware.CSRFContextKey) -} - -// SessionID returns the current session ID. -func (c *Context) SessionID() string { - return c.getContextStringValue(middleware.SessionIDContextKey) -} - -// UserSessionToken returns the current user session token. -func (c *Context) UserSessionToken() string { - return c.getContextStringValue(middleware.UserSessionTokenContextKey) -} - -// OAuth2State returns the current OAuth2 state. -func (c *Context) OAuth2State() string { - return c.getContextStringValue(middleware.OAuth2StateContextKey) -} - -// GenerateOAuth2State generate a new OAuth2 state. -func (c *Context) GenerateOAuth2State() string { - state := crypto.GenerateRandomString(32) - c.store.UpdateSessionField(c.SessionID(), "oauth2_state", state) - return state -} - -// SetFlashMessage defines a new flash message. -func (c *Context) SetFlashMessage(message string) { - c.store.UpdateSessionField(c.SessionID(), "flash_message", message) -} - -// FlashMessage returns the flash message and remove it. -func (c *Context) FlashMessage() string { - message := c.getContextStringValue(middleware.FlashMessageContextKey) - c.store.UpdateSessionField(c.SessionID(), "flash_message", "") - return message -} - -// SetFlashErrorMessage defines a new flash error message. -func (c *Context) SetFlashErrorMessage(message string) { - c.store.UpdateSessionField(c.SessionID(), "flash_error_message", message) -} - -// FlashErrorMessage returns the error flash message and remove it. -func (c *Context) FlashErrorMessage() string { - message := c.getContextStringValue(middleware.FlashErrorMessageContextKey) - c.store.UpdateSessionField(c.SessionID(), "flash_error_message", "") - return message -} - -func (c *Context) getContextStringValue(key *middleware.ContextKey) string { - if v := c.request.Context().Value(key); v != nil { - return v.(string) - } - - return "" -} - -// Route returns the path for the given arguments. -func (c *Context) Route(name string, args ...interface{}) string { - return route.Path(c.router, name, args...) -} - -// NewContext creates a new Context. -func NewContext(r *http.Request, store *storage.Storage, router *mux.Router, translator *locale.Translator) *Context { - return &Context{request: r, store: store, router: router, translator: translator} -} diff --git a/http/handler/handler.go b/http/handler/handler.go deleted file mode 100644 index 137ff11..0000000 --- a/http/handler/handler.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package handler - -import ( - "net/http" - "time" - - "github.com/miniflux/miniflux/config" - "github.com/miniflux/miniflux/locale" - "github.com/miniflux/miniflux/storage" - "github.com/miniflux/miniflux/template" - "github.com/miniflux/miniflux/timer" - - "github.com/gorilla/mux" -) - -// ControllerFunc is an application HTTP handler. -type ControllerFunc func(ctx *Context, request *Request, response *Response) - -// Handler manages HTTP handlers. -type Handler struct { - cfg *config.Config - store *storage.Storage - translator *locale.Translator - template *template.Engine - router *mux.Router -} - -// Use is a wrapper around an HTTP handler. -func (h *Handler) Use(f ControllerFunc) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer timer.ExecutionTime(time.Now(), r.URL.Path) - - ctx := NewContext(r, h.store, h.router, h.translator) - request := NewRequest(r) - response := NewResponse(h.cfg, w, r, h.template) - - f(ctx, request, response) - }) -} - -// NewHandler returns a new Handler. -func NewHandler(cfg *config.Config, store *storage.Storage, router *mux.Router, template *template.Engine, translator *locale.Translator) *Handler { - return &Handler{ - cfg: cfg, - store: store, - translator: translator, - router: router, - template: template, - } -} diff --git a/http/handler/html_response.go b/http/handler/html_response.go deleted file mode 100644 index 4d0dbe5..0000000 --- a/http/handler/html_response.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package handler - -import ( - "net/http" - - "github.com/miniflux/miniflux/logger" - "github.com/miniflux/miniflux/template" -) - -// HTMLResponse handles HTML responses. -type HTMLResponse struct { - writer http.ResponseWriter - request *http.Request - template *template.Engine -} - -// Render execute a template and send to the client the generated HTML. -func (h *HTMLResponse) Render(template, language string, args map[string]interface{}) { - h.writer.Header().Set("Content-Type", "text/html; charset=utf-8") - h.template.Render(h.writer, template, language, args) -} - -// ServerError sends a 500 error to the browser. -func (h *HTMLResponse) ServerError(err error) { - h.writer.Header().Set("Content-Type", "text/html; charset=utf-8") - h.writer.WriteHeader(http.StatusInternalServerError) - - if err != nil { - logger.Error("[Internal Server Error] %v", err) - h.writer.Write([]byte("Internal Server Error: " + err.Error())) - } else { - h.writer.Write([]byte("Internal Server Error")) - } -} - -// BadRequest sends a 400 error to the browser. -func (h *HTMLResponse) BadRequest(err error) { - h.writer.Header().Set("Content-Type", "text/html; charset=utf-8") - h.writer.WriteHeader(http.StatusBadRequest) - - if err != nil { - logger.Error("[Bad Request] %v", err) - h.writer.Write([]byte("Bad Request: " + err.Error())) - } else { - h.writer.Write([]byte("Bad Request")) - } -} - -// NotFound sends a 404 error to the browser. -func (h *HTMLResponse) NotFound() { - h.writer.Header().Set("Content-Type", "text/html; charset=utf-8") - h.writer.WriteHeader(http.StatusNotFound) - h.writer.Write([]byte("Page Not Found")) -} - -// Forbidden sends a 403 error to the browser. -func (h *HTMLResponse) Forbidden() { - h.writer.Header().Set("Content-Type", "text/html; charset=utf-8") - h.writer.WriteHeader(http.StatusForbidden) - h.writer.Write([]byte("Access Forbidden")) -} diff --git a/http/handler/json_response.go b/http/handler/json_response.go deleted file mode 100644 index a79268c..0000000 --- a/http/handler/json_response.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package handler - -import ( - "encoding/json" - "errors" - "net/http" - - "github.com/miniflux/miniflux/logger" -) - -// JSONResponse handles JSON responses. -type JSONResponse struct { - writer http.ResponseWriter - request *http.Request -} - -// Standard sends a JSON response with the status code 200. -func (j *JSONResponse) Standard(v interface{}) { - j.commonHeaders() - j.writer.WriteHeader(http.StatusOK) - j.writer.Write(j.toJSON(v)) -} - -// Created sends a JSON response with the status code 201. -func (j *JSONResponse) Created(v interface{}) { - j.commonHeaders() - j.writer.WriteHeader(http.StatusCreated) - j.writer.Write(j.toJSON(v)) -} - -// NoContent sends a JSON response with the status code 204. -func (j *JSONResponse) NoContent() { - j.commonHeaders() - j.writer.WriteHeader(http.StatusNoContent) -} - -// BadRequest sends a JSON response with the status code 400. -func (j *JSONResponse) BadRequest(err error) { - logger.Error("[Bad Request] %v", err) - j.commonHeaders() - j.writer.WriteHeader(http.StatusBadRequest) - - if err != nil { - j.writer.Write(j.encodeError(err)) - } -} - -// NotFound sends a JSON response with the status code 404. -func (j *JSONResponse) NotFound(err error) { - logger.Error("[Not Found] %v", err) - j.commonHeaders() - j.writer.WriteHeader(http.StatusNotFound) - j.writer.Write(j.encodeError(err)) -} - -// ServerError sends a JSON response with the status code 500. -func (j *JSONResponse) ServerError(err error) { - logger.Error("[Internal Server Error] %v", err) - j.commonHeaders() - j.writer.WriteHeader(http.StatusInternalServerError) - - if err != nil { - j.writer.Write(j.encodeError(err)) - } -} - -// Forbidden sends a JSON response with the status code 403. -func (j *JSONResponse) Forbidden() { - logger.Info("[API:Forbidden]") - j.commonHeaders() - j.writer.WriteHeader(http.StatusForbidden) - j.writer.Write(j.encodeError(errors.New("Access Forbidden"))) -} - -func (j *JSONResponse) commonHeaders() { - j.writer.Header().Set("Accept", "application/json") - j.writer.Header().Set("Content-Type", "application/json; charset=utf-8") -} - -func (j *JSONResponse) encodeError(err error) []byte { - type errorMsg struct { - ErrorMessage string `json:"error_message"` - } - - tmp := errorMsg{ErrorMessage: err.Error()} - data, err := json.Marshal(tmp) - if err != nil { - logger.Error("encoding error: %v", err) - } - - return data -} - -func (j *JSONResponse) toJSON(v interface{}) []byte { - b, err := json.Marshal(v) - if err != nil { - logger.Error("encoding error: %v", err) - return []byte("") - } - - return b -} - -// NewJSONResponse returns a new JSONResponse. -func NewJSONResponse(w http.ResponseWriter, r *http.Request) *JSONResponse { - return &JSONResponse{request: r, writer: w} -} diff --git a/http/handler/request.go b/http/handler/request.go deleted file mode 100644 index 7289a70..0000000 --- a/http/handler/request.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package handler - -import ( - "fmt" - "io" - "mime/multipart" - "net/http" - "strconv" - - "github.com/gorilla/mux" - "github.com/miniflux/miniflux/logger" -) - -// Request is a thin wrapper around "http.Request". -type Request struct { - request *http.Request -} - -// Request returns the raw Request struct. -func (r *Request) Request() *http.Request { - return r.request -} - -// Body returns the request body. -func (r *Request) Body() io.ReadCloser { - return r.request.Body -} - -// File returns uploaded file properties. -func (r *Request) File(name string) (multipart.File, *multipart.FileHeader, error) { - return r.request.FormFile(name) -} - -// Cookie returns the cookie value. -func (r *Request) Cookie(name string) string { - cookie, err := r.request.Cookie(name) - if err == http.ErrNoCookie { - return "" - } - - return cookie.Value -} - -// FormValue returns a form value as integer. -func (r *Request) FormValue(param string) string { - return r.request.FormValue(param) -} - -// FormIntegerValue returns a form value as integer. -func (r *Request) FormIntegerValue(param string) int64 { - value := r.request.FormValue(param) - integer, _ := strconv.Atoi(value) - return int64(integer) -} - -// IntegerParam returns an URL parameter as integer. -func (r *Request) IntegerParam(param string) (int64, error) { - vars := mux.Vars(r.request) - value, err := strconv.Atoi(vars[param]) - if err != nil { - logger.Error("[IntegerParam] %v", err) - return 0, fmt.Errorf("%s parameter is not an integer", param) - } - - if value < 0 { - return 0, nil - } - - return int64(value), nil -} - -// StringParam returns an URL parameter as string. -func (r *Request) StringParam(param, defaultValue string) string { - vars := mux.Vars(r.request) - value := vars[param] - if value == "" { - value = defaultValue - } - return value -} - -// QueryStringParam returns a querystring parameter as string. -func (r *Request) QueryStringParam(param, defaultValue string) string { - value := r.request.URL.Query().Get(param) - if value == "" { - value = defaultValue - } - return value -} - -// QueryIntegerParam returns a querystring parameter as string. -func (r *Request) QueryIntegerParam(param string, defaultValue int) int { - value := r.request.URL.Query().Get(param) - if value == "" { - return defaultValue - } - - val, err := strconv.Atoi(value) - if err != nil { - return defaultValue - } - - if val < 0 { - return defaultValue - } - - return val -} - -// HasQueryParam checks if the query string contains the given parameter. -func (r *Request) HasQueryParam(param string) bool { - values := r.request.URL.Query() - _, ok := values[param] - return ok -} - -// NewRequest returns a new Request. -func NewRequest(r *http.Request) *Request { - return &Request{r} -} diff --git a/http/handler/response.go b/http/handler/response.go deleted file mode 100644 index 4e4c44a..0000000 --- a/http/handler/response.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package handler - -import ( - "net/http" - "time" - - "github.com/miniflux/miniflux/config" - "github.com/miniflux/miniflux/template" -) - -// Response handles HTTP responses. -type Response struct { - cfg *config.Config - writer http.ResponseWriter - request *http.Request - template *template.Engine -} - -// SetCookie send a cookie to the client. -func (r *Response) SetCookie(cookie *http.Cookie) { - http.SetCookie(r.writer, cookie) -} - -// JSON returns a JSONResponse. -func (r *Response) JSON() *JSONResponse { - r.commonHeaders() - return NewJSONResponse(r.writer, r.request) -} - -// HTML returns a HTMLResponse. -func (r *Response) HTML() *HTMLResponse { - r.commonHeaders() - return &HTMLResponse{writer: r.writer, request: r.request, template: r.template} -} - -// XML returns a XMLResponse. -func (r *Response) XML() *XMLResponse { - r.commonHeaders() - return &XMLResponse{writer: r.writer, request: r.request} -} - -// Redirect redirects the user to another location. -func (r *Response) Redirect(path string) { - http.Redirect(r.writer, r.request, path, http.StatusFound) -} - -// NotModified sends a response with a 304 status code. -func (r *Response) NotModified() { - r.commonHeaders() - r.writer.WriteHeader(http.StatusNotModified) -} - -// Cache returns a response with caching headers. -func (r *Response) Cache(mimeType, etag string, content []byte, duration time.Duration) { - r.writer.Header().Set("Content-Type", mimeType) - r.writer.Header().Set("ETag", etag) - r.writer.Header().Set("Cache-Control", "public") - r.writer.Header().Set("Expires", time.Now().Add(duration).Format(time.RFC1123)) - - if etag == r.request.Header.Get("If-None-Match") { - r.writer.WriteHeader(http.StatusNotModified) - } else { - r.writer.Write(content) - } -} - -func (r *Response) commonHeaders() { - r.writer.Header().Set("X-XSS-Protection", "1; mode=block") - r.writer.Header().Set("X-Content-Type-Options", "nosniff") - r.writer.Header().Set("X-Frame-Options", "DENY") - - // Even if the directive "frame-src" has been deprecated in Firefox, - // we keep it to stay compatible with other browsers. - r.writer.Header().Set("Content-Security-Policy", "default-src 'self'; img-src *; media-src *; frame-src *; child-src *") - - if r.cfg.IsHTTPS && r.cfg.HasHSTS() { - r.writer.Header().Set("Strict-Transport-Security", "max-age=31536000") - } -} - -// NewResponse returns a new Response. -func NewResponse(cfg *config.Config, w http.ResponseWriter, r *http.Request, template *template.Engine) *Response { - return &Response{cfg: cfg, writer: w, request: r, template: template} -} diff --git a/http/handler/xml_response.go b/http/handler/xml_response.go deleted file mode 100644 index 5b647a1..0000000 --- a/http/handler/xml_response.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package handler - -import ( - "fmt" - "net/http" -) - -// XMLResponse handles XML responses. -type XMLResponse struct { - writer http.ResponseWriter - request *http.Request -} - -// Download force the download of a XML document. -func (x *XMLResponse) Download(filename, data string) { - x.writer.Header().Set("Content-Type", "text/xml") - x.writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) - x.writer.Write([]byte(data)) -} - -// Serve forces the XML to be sent to browser. -func (x *XMLResponse) Serve(data string) { - x.writer.Header().Set("Content-Type", "text/xml") - x.writer.Write([]byte(data)) -} diff --git a/http/request/request.go b/http/request/request.go new file mode 100644 index 0000000..da79a3f --- /dev/null +++ b/http/request/request.go @@ -0,0 +1,90 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package request + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/gorilla/mux" +) + +// Cookie returns the cookie value. +func Cookie(r *http.Request, name string) string { + cookie, err := r.Cookie(name) + if err == http.ErrNoCookie { + return "" + } + + return cookie.Value +} + +// FormIntValue returns a form value as integer. +func FormIntValue(r *http.Request, param string) int64 { + value := r.FormValue(param) + integer, _ := strconv.Atoi(value) + return int64(integer) +} + +// IntParam returns an URL route parameter as integer. +func IntParam(r *http.Request, param string) (int64, error) { + vars := mux.Vars(r) + value, err := strconv.Atoi(vars[param]) + if err != nil { + return 0, fmt.Errorf("request: %s parameter is not an integer", param) + } + + if value < 0 { + return 0, nil + } + + return int64(value), nil +} + +// Param returns an URL route parameter as string. +func Param(r *http.Request, param, defaultValue string) string { + vars := mux.Vars(r) + value := vars[param] + if value == "" { + value = defaultValue + } + return value +} + +// QueryParam returns a querystring parameter as string. +func QueryParam(r *http.Request, param, defaultValue string) string { + value := r.URL.Query().Get(param) + if value == "" { + value = defaultValue + } + return value +} + +// QueryIntParam returns a querystring parameter as integer. +func QueryIntParam(r *http.Request, param string, defaultValue int) int { + value := r.URL.Query().Get(param) + if value == "" { + return defaultValue + } + + val, err := strconv.Atoi(value) + if err != nil { + return defaultValue + } + + if val < 0 { + return defaultValue + } + + return val +} + +// HasQueryParam checks if the query string contains the given parameter. +func HasQueryParam(r *http.Request, param string) bool { + values := r.URL.Query() + _, ok := values[param] + return ok +} diff --git a/http/response/html/html.go b/http/response/html/html.go new file mode 100644 index 0000000..4f605e8 --- /dev/null +++ b/http/response/html/html.go @@ -0,0 +1,57 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package html + +import ( + "net/http" + + "github.com/miniflux/miniflux/logger" +) + +// OK writes a standard HTML response. +func OK(w http.ResponseWriter, b []byte) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Write(b) +} + +// ServerError sends a 500 error to the browser. +func ServerError(w http.ResponseWriter, err error) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + + if err != nil { + logger.Error("[Internal Server Error] %v", err) + w.Write([]byte("Internal Server Error: " + err.Error())) + } else { + w.Write([]byte("Internal Server Error")) + } +} + +// BadRequest sends a 400 error to the browser. +func BadRequest(w http.ResponseWriter, err error) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusBadRequest) + + if err != nil { + logger.Error("[Bad Request] %v", err) + w.Write([]byte("Bad Request: " + err.Error())) + } else { + w.Write([]byte("Bad Request")) + } +} + +// NotFound sends a 404 error to the browser. +func NotFound(w http.ResponseWriter) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("Page Not Found")) +} + +// Forbidden sends a 403 error to the browser. +func Forbidden(w http.ResponseWriter) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("Access Forbidden")) +} diff --git a/http/response/json/json.go b/http/response/json/json.go new file mode 100644 index 0000000..ff0f06b --- /dev/null +++ b/http/response/json/json.go @@ -0,0 +1,107 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package json + +import ( + "encoding/json" + "errors" + "net/http" + + "github.com/miniflux/miniflux/logger" +) + +// OK sends a JSON response with the status code 200. +func OK(w http.ResponseWriter, v interface{}) { + commonHeaders(w) + w.WriteHeader(http.StatusOK) + w.Write(toJSON(v)) +} + +// Created sends a JSON response with the status code 201. +func Created(w http.ResponseWriter, v interface{}) { + commonHeaders(w) + w.WriteHeader(http.StatusCreated) + w.Write(toJSON(v)) +} + +// NoContent sends a JSON response with the status code 204. +func NoContent(w http.ResponseWriter) { + commonHeaders(w) + w.WriteHeader(http.StatusNoContent) +} + +// NotFound sends a JSON response with the status code 404. +func NotFound(w http.ResponseWriter, err error) { + logger.Error("[Not Found] %v", err) + commonHeaders(w) + w.WriteHeader(http.StatusNotFound) + w.Write(encodeError(err)) +} + +// ServerError sends a JSON response with the status code 500. +func ServerError(w http.ResponseWriter, err error) { + logger.Error("[Internal Server Error] %v", err) + commonHeaders(w) + w.WriteHeader(http.StatusInternalServerError) + + if err != nil { + w.Write(encodeError(err)) + } +} + +// Forbidden sends a JSON response with the status code 403. +func Forbidden(w http.ResponseWriter) { + logger.Info("[Forbidden]") + commonHeaders(w) + w.WriteHeader(http.StatusForbidden) + w.Write(encodeError(errors.New("Access Forbidden"))) +} + +// Unauthorized sends a JSON response with the status code 401. +func Unauthorized(w http.ResponseWriter) { + commonHeaders(w) + w.WriteHeader(http.StatusUnauthorized) + w.Write(encodeError(errors.New("Access Unauthorized"))) +} + +// BadRequest sends a JSON response with the status code 400. +func BadRequest(w http.ResponseWriter, err error) { + logger.Error("[Bad Request] %v", err) + commonHeaders(w) + w.WriteHeader(http.StatusBadRequest) + + if err != nil { + w.Write(encodeError(err)) + } +} + +func commonHeaders(w http.ResponseWriter) { + w.Header().Set("Accept", "application/json") + w.Header().Set("Content-Type", "application/json; charset=utf-8") +} + +func encodeError(err error) []byte { + type errorMsg struct { + ErrorMessage string `json:"error_message"` + } + + tmp := errorMsg{ErrorMessage: err.Error()} + data, err := json.Marshal(tmp) + if err != nil { + logger.Error("json encoding error: %v", err) + } + + return data +} + +func toJSON(v interface{}) []byte { + b, err := json.Marshal(v) + if err != nil { + logger.Error("json encoding error: %v", err) + return []byte("") + } + + return b +} diff --git a/http/response/response.go b/http/response/response.go new file mode 100644 index 0000000..066d061 --- /dev/null +++ b/http/response/response.go @@ -0,0 +1,34 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package response + +import ( + "net/http" + "time" +) + +// Redirect redirects the user to another location. +func Redirect(w http.ResponseWriter, r *http.Request, path string) { + http.Redirect(w, r, path, http.StatusFound) +} + +// NotModified sends a response with a 304 status code. +func NotModified(w http.ResponseWriter) { + w.WriteHeader(http.StatusNotModified) +} + +// Cache returns a response with caching headers. +func Cache(w http.ResponseWriter, r *http.Request, mimeType, etag string, content []byte, duration time.Duration) { + w.Header().Set("Content-Type", mimeType) + w.Header().Set("ETag", etag) + w.Header().Set("Cache-Control", "public") + w.Header().Set("Expires", time.Now().Add(duration).Format(time.RFC1123)) + + if etag == r.Header.Get("If-None-Match") { + w.WriteHeader(http.StatusNotModified) + } else { + w.Write(content) + } +} diff --git a/http/response/xml/xml.go b/http/response/xml/xml.go new file mode 100644 index 0000000..9e37e87 --- /dev/null +++ b/http/response/xml/xml.go @@ -0,0 +1,23 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package xml + +import ( + "fmt" + "net/http" +) + +// OK sends a XML document. +func OK(w http.ResponseWriter, data string) { + w.Header().Set("Content-Type", "text/xml") + w.Write([]byte(data)) +} + +// Attachment forces the download of a XML document. +func Attachment(w http.ResponseWriter, filename, data string) { + w.Header().Set("Content-Type", "text/xml") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) + w.Write([]byte(data)) +} |