With a single API resource /, we have written only one handler that process GET & POST request on API resource /
POST we use to create a resource in database, byt sending data in request body
PUT we use to update an existing resource in database
My understanding is, RESTful best practice says, a handler need to serve an API resource(say /) for all requests GET, POST & PUT
We want the same handler to process PUT request, but the API resource will be something like /1234, where 1234 is existing id
Technically, API resource /1234 will also map to same handler that processes /, but,
From RESTful best practices, Does /1234 need to be handled without passing id as part of API resource URI? something like below...
func ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet { // for API resource '/'
p.getProducts(w, r)
return
}
if r.Method == http.MethodPost { // for API resource '/'
p.addProduct(w, r)
return
}
if r.Method == http.MethodPut { // for API resource '/'
p.updateProduct(w, r)
return
}
}
func updateProduct(w http.ResponseWriter, r *http.Request) {
var idString string
decoder := json.NewDecoder(r.Body)
decoder.Decode(idString)
id, err := findID(idString)
// do whatever with id
}
func findID(str string) (int, error) {
dfa := regexp.MustCompile(`/([0-9]+)`)
matches := dfa.FindAllStringSubmatch(str, -1) // returns [][]string
idString := matches[0][1]
id, err := strconv.Atoi(idString)
return id, nil
}
As I understood right you right.
You have two call which can be handle without Id for end point /.
One is POST when the back-end with generate you Id as a result.
Second is GET for all resources but this is up to you. Maybe because of secure reason you would not like to list all available resources.
One extra information is that PUT & 'POST' can use the same handler but logic in handler has to check if 'id' is provided and do extra more logic to create resource.
Related
I'm trying to build a web service which would receive an HTTP PATCH request to update some fields in a "users" table (so I don't know how many nor which ones have to be updated).
I decided to work with Gin and because I'm working with Postgres, I'm using pgx.
The request...
PATCH {{protocol}}{{host}}/update-user/USER_ID_XXX HTTP/1.1
content-type: application/json
Authorization: Bearer : TOKEN_XXX
{
"review_count": 2,
"rating": 2.2,
}
... is handled by the Patch method
func (handler *userHandler) Patch(c *gin.Context) {
reqBody := &users.User{}
err := c.BindJSON(reqBody) <------ BREAKS
if err != nil {
c.JSON(http.StatusBadRequest, rest_errors.NewInternalServerError("JSON Binding Failed while trying to patch user", err))
return
}
...
The User struct makes uses of pgtypes. This is because, in the context of a GET request, if I retrieve a User from Postgres who has a review_count field set to null, i get an error when trying to marshal the result. So the pgtype.Int struct encapsulate the original value, a few other fields and contains a MarshalJSON() function to do the job properly ( see the MarshalJSON function here in pgtype.Int2 )
user_dto.go
type User struct {
Id string `json:"id"`
...
ReviewCount pgtype.Int2 `json:"review_count"`
Rating float32 `json:"rating"`
...
}
Back to our PATCH request, the problem naturally comes in when I receive a field that is "pgtyped" like review_count .... Gin tries to unmarshal it and fails because it parses an int value and he's asked to put it into the pgtype.Int2 struct. So when I call err := c.BindJSON(reqBody) I get this error
"json: cannot unmarshal number into Go struct field
User.ReviewCount of type pgtype.Int2"
I never had to implement this kind of feature before and I must confess that I know nothing about the "best options" if only I knew one that is working :D
I seem to get empty body content of a Go http.Request if the method is DELETE. But if I change the method to POST, then the body content gives me the content I expect.
The relevant code from my golang looks like this:
import(
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
func Delete(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
qs := r.Form
log.Println(qs)
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/profile", Delete).Methods("POST")
router.HandleFunc("/profile", Delete).Methods("DELETE")
}
Now when I run this JavaScript code form my browser:
fetch(sendurl,{
method:"POST",
headers:{
'Content-Type': 'application/x-www-form-urlencoded'
},
body:"data="+project.encodeFormURIComponent(JSON.stringify({"ids":[1032,1033]}))
})
.then(response=>{
if(response.ok)
return response.json();
})
.then(result=>{
console.log(result);
})
I see a nice array of numbers in my qs[ids] in my Golang code. But if I change my method:"POST" to method:"DELETE" in the JavaScript, then qs is empty.
What am I doing wrong?
UPDATE
This JavaScript with the DELETE method can populate the Go qs variable the way one would normally expect:
fetch(sendurl+"?data="+project.encodeFormURIComponent(JSON.stringify({"ids":[1032,1033]})),{
method:"DELETE",
headers:{
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.then(response=>{
if(response.ok)
return response.json();
})
.then(result=>{
console.log(result);
})
So it seems Go will ignore JavaScript body argument when DELETE method is used, but it will respect the query string content in the API endpoint url? Why is it like that?
https://www.rfc-editor.org/rfc/rfc7231#section-4.3.5
A payload within a DELETE request message has no defined semantics; sending a payload body on a DELETE request might cause some existing implementations to reject the request.
The query string is part of the target-uri of the request; in other words, the query string is part of the identifier, not an incidental modifier of it. But the message-body of the request is not part of the identifier.
So your local framework, or any of the other general purpose components forwarding your request, are not required to provide support for the message-body.
Think "undefined behavior" in C.
From the Kubernetes documentation on authorization it states that:
When multiple authorization modules are configured, each is checked in sequence. If any authorizer approves or denies a request, that decision is immediately returned and no other authorizer is consulted. If all modules have no opinion on the request, then the request is denied. A deny returns an HTTP status code 403.
I am now writing a custom webhook for authorization and I would want the logic to fallback to RBAC for a few cases - i.e. have my webhook respond with what the documentation refers to as "no opinion". The documentation however only details how to approve or deny a request and doesn't come back to this third option which seems essential for having multiple authorization modules checked in sequence. How would I best in the context of my webhook respond with "I have no opinion on this request, please pass it on to the next authorizer"?
It's not clear how multiple AuthorizationModule work from kubernetes official doc.
So I check the source code of apiserver, it create a combine authorizer.Authorizer by union.New(authorizers...), from union source I find the answer:
The union authorizer iterates over each subauthorizer and returns the first decision that is either an Allow decision or a Deny decision. If a subauthorizer returns a NoOpinion, then the union authorizer moves onto the next authorizer or, if the subauthorizer was the last authorizer, returns NoOpinion as the aggregate decision
More detail at k8s.io/apiserver/pkg/authorization/union:
func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
var (
errlist []error
reasonlist []string
)
for _, currAuthzHandler := range authzHandler {
decision, reason, err := currAuthzHandler.Authorize(a)
if err != nil {
errlist = append(errlist, err)
}
if len(reason) != 0 {
reasonlist = append(reasonlist, reason)
}
switch decision {
case authorizer.DecisionAllow, authorizer.DecisionDeny:
return decision, reason, err
case authorizer.DecisionNoOpinion:
// continue to the next authorizer
}
}
return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
}
So if you want to create your custom webhook AuthozitaionModule, if you want to pass decision to next authorizer, just give permissive response like:
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"status": {
"reason": "no decision",
"allowed": false,
"denied": false
}
}
Then apiserver can make a decision by this reponse:
switch {
case r.Status.Denied && r.Status.Allowed:
return authorizer.DecisionDeny, r.Status.Reason, fmt.Errorf("webhook subject access review returned both allow and deny response")
case r.Status.Denied:
return authorizer.DecisionDeny, r.Status.Reason, nil
case r.Status.Allowed:
return authorizer.DecisionAllow, r.Status.Reason, nil
default:
return authorizer.DecisionNoOpinion, r.Status.Reason, nil
}
I am using auth0 and golang for a rest service that is similar implemented as shown here.
I wonder how I can find out the name of the user that is currently triggering a certain API call - for instance if someone requests http://localhost:3000/products - the go handler in this case looks like this:
var ProductsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
payload, _ := json.Marshal(products)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(payload))
})
Does the request r contain more information about the current user?
Or do I need to find out the current user in the middleware authentication:
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
secret := []byte("{YOUR-AUTH0-API-SECRET}")
secretProvider := auth0.NewKeyProvider(secret)
audience := "{YOUR-AUTH0-API-AUDIENCE}"
configuration := auth0.NewConfiguration(secretProvider, audience, "https://{YOUR-AUTH0-DOMAIN}.auth0.com/", jose.HS256)
validator := auth0.NewValidator(configuration)
token, err := validator.ValidateRequest(r)
if err != nil {
fmt.Println(err)
fmt.Println("Token is not valid:", token)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorized"))
} else {
next.ServeHTTP(w, r)
}
})
}
Does the token contain more information about the user?
I am a bit lost here. auth0 works perfectly to ensure that only registered persons can use the REST-API, but I want to deliver user specific information. So it depends on the current user what a REST call is handing back. Initially, I was thinking that auth0 would take care of this. Is there a simple way to achieve this?
Yes, you need to use token to get information about request issue.
To sort all you want you need to take a look to next:
Check out how token extracted in this method: token extraction
And the Claims here: Claims structure
And how combine it here: retrieve Claims
The claims have a field
Issuer string `json:"iss,omitempty"`
you are interested in.
I am interested in dynamically taking arguments from the user as input through a browser or a CLI to pass in those parameters to the REST API call and hence construct the URL dynamically using Go which is going to ultimately fetch me some JSON data.
I want to know some techniques in Go which could help me do that. One ideal way I thought was to use a map and populate it with arguments keys and corresponding values and iterate over it and append it to the URL string. But when it comes to dynamically taking the arguments and populating the map, I am not very sure how to do that in Go. Can someone help me out with some code snippet in Go?
Request example:
http://<IP>:port?api=fetchJsonData&arg1=val1&arg2=val2&arg3=val3.....&argn=valn
There's already url.URL that handles that kind of things for you.
For http handlers (incoming requests) it's a part of http.Request (access it with req.URL.Query()).
A very good example from the official docs:
u, err := url.Parse("http://bing.com/search?q=dotnet")
if err != nil {
log.Fatal(err)
}
u.Scheme = "https"
u.Host = "google.com"
q := u.Query()
q.Set("q", "golang")
u.RawQuery = q.Encode()
fmt.Println(u)
https://github.com/golang/go/issues/17340#issuecomment-251537687
https://play.golang.org/p/XUctl_odTSb
package main
import (
"fmt"
"net/url"
)
func someURL() string {
url := url.URL{
Scheme: "https",
Host: "example.com",
}
return url.String()
}
func main() {
fmt.Println(someURL())
}
returns:
https://example.com
url.Values{} provides an interface for building query params. You can construct inline and/or use .Add for dynamic properties:
queryParams := url.Values{
"checkin": {request.CheckIn},
"checkout": {request.CheckOut},
}
if request.ReservationId {
queryParams.Add("reservationId", request.ReservationId)
}
url := "https://api.example?" + queryParams.Encode() // checkin=...&checkout=...