Skip to content

Commit

Permalink
fix(handler): requested format with Accept header and suffix
Browse files Browse the repository at this point in the history
  • Loading branch information
nick-rv committed Nov 16, 2022
1 parent 2f49abb commit facbc5e
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 118 deletions.
87 changes: 52 additions & 35 deletions internal/api/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,23 +75,10 @@ func RequestedFormat(r *http.Request) string {
// first check explicit path
path := r.URL.EscapedPath()

// Accept header value
hdrAcceptValue := r.Header.Get("Accept")

// Extension value
splittedPath := strings.Split(path, "/")
pathEnd := splittedPath[len(splittedPath)-1]
extension := ""
pos := strings.LastIndex(pathEnd, ".")
if pos != -1 {
extension = pathEnd[pos+1:]
}

// TODO: case when extension and header Accept are provided at the same time
// -> Bad Request ?

if extension != "" && hdrAcceptValue == "" {
switch extension {
suffix := PathSuffix(path)
if suffix != "" {
switch suffix {
case "json":
return FormatJSON
case "html":
Expand All @@ -101,24 +88,41 @@ func RequestedFormat(r *http.Request) string {
case "svg":
return FormatSVG
default:
return extension
return suffix
}
}

if hdrAcceptValue != "" {
switch hdrAcceptValue {
case ContentTypeJSON:
return FormatJSON
case ContentTypeSchemaJSON, ContentTypeSchemaPatchJSON:
return FormatSchemaJSON
case ContentTypeHTML:
return FormatHTML
case ContentTypeText:
return FormatText
case ContentTypeSVG:
return FormatSVG
default:
return hdrAcceptValue
} else {
// Accept header value
hdrAcceptValue := r.Header.Get("Accept")
if hdrAcceptValue != "" {
// Accept header fields preferences:
// -> https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.1

// Examples:
// "Accept: application/json"
// "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
preferredFormats := strings.Split(hdrAcceptValue, ",")

for _, value := range preferredFormats {
mediaTypeValue := value
lastSemicolon := strings.LastIndex(value, ";")
if lastSemicolon > 0 {
mediaTypeValue = value[:lastSemicolon] // 'q' quality parameter not used
}
switch mediaTypeValue {
case ContentTypeJSON:
return FormatJSON
case ContentTypeSchemaJSON, ContentTypeSchemaPatchJSON:
return FormatSchemaJSON
case ContentTypeHTML:
return FormatHTML
case ContentTypeText:
return FormatText
case ContentTypeSVG:
return FormatSVG
default:
return hdrAcceptValue
}
}
}
}
return FormatJSON
Expand All @@ -139,12 +143,25 @@ func SentDataFormat(r *http.Request) string {

// PathStripFormat removes a format extension from a path
func PathStripFormat(path string) string {
if strings.HasSuffix(path, ".html") || strings.HasSuffix(path, ".json") {
return path[0 : len(path)-5]
pos := strings.LastIndex(path, ".")
if pos != -1 {
path = path[:pos]
}
return path
}

// PathSuffix returns the format extension from a path following a dot character
func PathSuffix(path string) string {
splittedPath := strings.Split(path, "/")
pathEnd := splittedPath[len(splittedPath)-1]
pos := strings.LastIndex(pathEnd, ".")
if pos != -1 {
return pathEnd[pos+1:]
} else {
return ""
}
}

// URLQuery gets the query part of a URL
func URLQuery(url *url.URL) string {
uri := url.RequestURI()
Expand Down
102 changes: 53 additions & 49 deletions internal/service/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ import (
)

const (
routeVarID = "id"
routeVarFeatureID = "fid"
routeVarStrongEtag = "etag"
routeVarCollectionID = "cid"
routeVarFeatureID = "fid"
routeVarFunctionID = "funid"
routeVarStrongEtag = "etag"
)

func InitRouter(basePath string) *mux.Router {
Expand All @@ -52,9 +53,13 @@ func InitRouter(basePath string) *mux.Router {
Subrouter()

addRoute(router, "/", handleRoot)
addRoute(router, "/home{.fmt}", handleRoot)

addRoute(router, "/home", handleRoot)
addRoute(router, "/home.{fmt}", handleRoot)

// consistent with pg_tileserv
addRoute(router, "/index{.fmt}", handleRoot)
addRoute(router, "/index", handleRoot)
addRoute(router, "/index.{fmt}", handleRoot)

addRoute(router, "/etags/decodestrong/{etag}", handleDecodeStrongEtag)

Expand All @@ -67,33 +72,27 @@ func InitRouter(basePath string) *mux.Router {
addRoute(router, "/collections", handleCollections)
addRoute(router, "/collections.{fmt}", handleCollections)

addRoute(router, "/collections/{id}", handleCollection)
addRoute(router, "/collections/{id}.{fmt}", handleCollection)
addRoute(router, "/collections/{cid}", handleCollection)

addRoute(router, "/collections/{id}/items", handleCollectionItems)
addRoute(router, "/collections/{id}/items.{fmt}", handleCollectionItems)
addRouteWithMethod(router, "/collections/{id}/items", handleCreateCollectionItem, "POST")
addRouteWithMethod(router, "/collections/{id}/items/{fid}", handleDeleteCollectionItem, "DELETE")
addRoute(router, "/collections/{cid}/items", handleCollectionItems)
addRoute(router, "/collections/{cid}/items.{fmt}", handleCollectionItems)
addRouteWithMethod(router, "/collections/{cid}/items", handleCreateCollectionItem, "POST")
addRouteWithMethod(router, "/collections/{cid}/items/{fid}", handleDeleteCollectionItem, "DELETE")

addRoute(router, "/collections/{id}/schema", handleCollectionSchemas)
addRoute(router, "/collections/{cid}/schema", handleCollectionSchemas)

addRoute(router, "/collections/{id}/items/{fid}", handleItem)
addRoute(router, "/collections/{id}/items/{fid}.{fmt}", handleItem)
addRoute(router, "/collections/{cid}/items/{fid}", handleItem)

addRouteWithMethod(router, "/collections/{id}/items/{fid}", handlePartialUpdateItem, "PATCH")
addRouteWithMethod(router, "/collections/{id}/items/{fid}.{fmt}", handlePartialUpdateItem, "PATCH")
addRouteWithMethod(router, "/collections/{cid}/items/{fid}", handlePartialUpdateItem, "PATCH")

addRouteWithMethod(router, "/collections/{id}/items/{fid}", handleReplaceItem, "PUT")
addRouteWithMethod(router, "/collections/{id}/items/{fid}.{fmt}", handleReplaceItem, "PUT")
addRouteWithMethod(router, "/collections/{cid}/items/{fid}", handleReplaceItem, "PUT")

addRoute(router, "/functions", handleFunctions)
addRoute(router, "/functions.{fmt}", handleFunctions)

addRoute(router, "/functions/{id}", handleFunction)
addRoute(router, "/functions/{id}.{fmt}", handleFunction)
addRoute(router, "/functions/{funid}", handleFunction)

addRoute(router, "/functions/{id}/items", handleFunctionItems)
addRoute(router, "/functions/{id}/items.{fmt}", handleFunctionItems)
addRoute(router, "/functions/{funid}/items", handleFunctionItems)

return router
}
Expand Down Expand Up @@ -175,7 +174,7 @@ func linkAlt(urlBase string, path string, desc string) *api.Link {

func handleDecodeStrongEtag(w http.ResponseWriter, r *http.Request) *appError {
//--- extract request parameters
etag := getRequestVar(routeVarStrongEtag, r)
etag := getRequestVarStrip(routeVarStrongEtag, r)
decodedEtag, err := api.DecodeStrongEtag(etag)
if err != nil {
return appErrorBadRequest(err, "Malformed etag")
Expand Down Expand Up @@ -268,7 +267,9 @@ func handleCollection(w http.ResponseWriter, r *http.Request) *appError {
format := api.RequestedFormat(r)
urlBase := serveURLBase(r)

name := getRequestVar(routeVarID, r)
// the collection is at the end of the URL, this is why we strip the extension
// it may be an issue if the schema name is provided here
name := getRequestVarStrip(routeVarCollectionID, r)

tbl, err := catalogInstance.TableByName(name)
if tbl == nil && err == nil {
Expand Down Expand Up @@ -306,7 +307,7 @@ func handleCollectionSchemas(w http.ResponseWriter, r *http.Request) *appError {
format := api.RequestedFormat(r)

//--- extract request parameters
name := getRequestVar(routeVarID, r)
name := getRequestVar(routeVarCollectionID, r)
tbl, err1 := catalogInstance.TableByName(name)
if err1 != nil {
return appErrorInternal(err1, api.ErrMsgCollectionAccess, name)
Expand Down Expand Up @@ -346,7 +347,7 @@ func handleCreateCollectionItem(w http.ResponseWriter, r *http.Request) *appErro
urlBase := serveURLBase(r)

//--- extract request parameters
name := getRequestVar(routeVarID, r)
name := getRequestVar(routeVarCollectionID, r)

//--- check query parameters
queryValues := r.URL.Query()
Expand Down Expand Up @@ -395,8 +396,8 @@ func handleCreateCollectionItem(w http.ResponseWriter, r *http.Request) *appErro
func handleDeleteCollectionItem(w http.ResponseWriter, r *http.Request) *appError {

//--- extract request parameters
name := getRequestVar(routeVarID, r)
fid := getRequestVar(routeVarFeatureID, r)
name := getRequestVar(routeVarCollectionID, r)
fid := getRequestVarStrip(routeVarFeatureID, r)

//--- check request parameters
index, err := strconv.Atoi(fid)
Expand Down Expand Up @@ -430,13 +431,14 @@ func handleDeleteCollectionItem(w http.ResponseWriter, r *http.Request) *appErro
}

func handleCollectionItems(w http.ResponseWriter, r *http.Request) *appError {
// "/collections/{id}/items"
// TODO: determine content from request header?
format := api.RequestedFormat(r)
urlBase := serveURLBase(r)
query := api.URLQuery(r.URL)

//--- extract request parameters
name := getRequestVar(routeVarID, r)
name := getRequestVar(routeVarCollectionID, r)
reqParam, err := parseRequestParams(r)
if err != nil {
return appErrorBadRequest(err, err.Error())
Expand All @@ -449,20 +451,22 @@ func handleCollectionItems(w http.ResponseWriter, r *http.Request) *appError {
if tbl == nil {
return appErrorNotFound(err1, api.ErrMsgCollectionNotFound, name)
}
param, err := createQueryParams(&reqParam, tbl.Columns, tbl.Srid)
if err != nil {
return appErrorBadRequest(err, err.Error())
}
param, errQuery := createQueryParams(&reqParam, tbl.Columns, tbl.Srid)
param.Filter = parseFilter(reqParam.Values, tbl.DbTypes)

ctx := r.Context()
switch format {
case api.FormatJSON:
return writeItemsJSON(ctx, w, name, param, urlBase)
case api.FormatHTML:
return writeItemsHTML(w, tbl, name, query, urlBase)
if errQuery == nil {
ctx := r.Context()
switch format {
case api.FormatJSON:
return writeItemsJSON(ctx, w, name, param, urlBase)
case api.FormatHTML:
return writeItemsHTML(w, tbl, name, query, urlBase)
default:
return appErrorNotAcceptable(nil, api.ErrMsgNotSupportedFormat, format)
}
} else {
return appErrorBadRequest(errQuery, api.ErrMsgInvalidQuery)
}
return nil
}

func writeCreateItemSchemaJSON(ctx context.Context, w http.ResponseWriter, table *api.Table) *appError {
Expand Down Expand Up @@ -636,8 +640,8 @@ func handleItem(w http.ResponseWriter, r *http.Request) *appError {

query := api.URLQuery(r.URL)
//--- extract request parameters
name := getRequestVar(routeVarID, r)
fid := getRequestVar(routeVarFeatureID, r)
name := getRequestVar(routeVarCollectionID, r)
fid := getRequestVarStrip(routeVarFeatureID, r)
reqParam, err := parseRequestParams(r)
if err != nil {
return appErrorBadRequest(err, err.Error())
Expand Down Expand Up @@ -694,8 +698,8 @@ func handleItem(w http.ResponseWriter, r *http.Request) *appError {

func handlePartialUpdateItem(w http.ResponseWriter, r *http.Request) *appError {
// extract request parameters
name := getRequestVar(routeVarID, r)
fid := getRequestVar(routeVarFeatureID, r)
name := getRequestVar(routeVarCollectionID, r)
fid := getRequestVarStrip(routeVarFeatureID, r)

// check query parameters
queryValues := r.URL.Query()
Expand Down Expand Up @@ -749,8 +753,8 @@ func handlePartialUpdateItem(w http.ResponseWriter, r *http.Request) *appError {
func handleReplaceItem(w http.ResponseWriter, r *http.Request) *appError {

// extract request parameters
name := getRequestVar(routeVarID, r)
fid := getRequestVar(routeVarFeatureID, r)
name := getRequestVar(routeVarCollectionID, r)
fid := getRequestVarStrip(routeVarFeatureID, r)

// check query parameters
queryValues := r.URL.Query()
Expand Down Expand Up @@ -973,7 +977,7 @@ func handleFunction(w http.ResponseWriter, r *http.Request) *appError {
format := api.RequestedFormat(r)
urlBase := serveURLBase(r)

shortName := getRequestVar(routeVarID, r)
shortName := getRequestVarStrip(routeVarFunctionID, r)
name := data.FunctionQualifiedId(shortName)

fn, err := catalogInstance.FunctionByName(name)
Expand Down Expand Up @@ -1014,7 +1018,7 @@ func handleFunctionItems(w http.ResponseWriter, r *http.Request) *appError {
urlBase := serveURLBase(r)

//--- extract request parameters
name := data.FunctionQualifiedId(getRequestVar(routeVarID, r))
name := data.FunctionQualifiedId(getRequestVarStrip(routeVarFunctionID, r))
reqParam, err := parseRequestParams(r)
if err != nil {
return appErrorBadRequest(err, err.Error())
Expand All @@ -1030,7 +1034,7 @@ func handleFunctionItems(w http.ResponseWriter, r *http.Request) *appError {
return appErrorBadRequest(err, err.Error())
}
fnArgs := restrict(reqParam.Values, fn.InNames)
//log.Debugf("Function request args: %v ", fnArgs)
// log.Debugf("Function request args: %v ", fnArgs)

ctx := r.Context()
switch format {
Expand Down
Loading

0 comments on commit facbc5e

Please sign in to comment.