I wonder how to handle endpoint with given empty parameter, for example one endpoint of my API is
...url/item/{id}
and it's implementation is
func GetItem(c *gin.Context){
id := c.Param("id")
...
}
Should I handle empty id by
if id == ""{
//handle if empty
return
}
or it's not necessary to validate it?
I think about it because you can call endpoint method from unit test and the question is if it's possible to hack it somehow and call endpoint with empty id, which may cause some fatal errors?
You should prevent it at your routing layer rather than when you're getting the path parameters.
router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
In this snippet from the gin documentation it basically describes a similar get endpoint where it expects you to have a :name parameter, which maps to your id parameter. If the route is invoked without an id parameter it'll not route to that function.
Related
I am using a the emicklei/go-restful framework to deal with rest API.
I wish to access the route path from the request. Meaning that when I configure a dummy route like this:
ws := new(restful.WebService)
ws.Path("/resources/names")
ws.Route(ws.GET("/{name}").To(getName))
restful.DefaultContainer.Add(ws)
I wish to access the information that the route was "/resources/names/{name}"
I can access the actual URL which is call by calling:
req.Request.URL.Path
But this will return the specific URL, not the generic one.
Any suggestion?
After more research I finally found that the method req.SelectedRoutePath() will return expected value.
I'm using labstack Echo (v4.1.11) go framework to create a REST API.
How do we/can we pass map query param in the URL? I tried adding the following struct for the request body
PaginationRequest struct {
Page int64 `query:"page"`
Limit int64 `query:"limit"`
Filter map[string]string `query:"filter"`
}
and hoping I could got this url http://host/endpoint?page=1&limit=10&filter[status]=1&filter[user_id]=12 to work (read the filter["status"] and filter["user_id"]
But when I tried binding the incoming request to PaginationRequest it only read the page and limit parameter and filter = nil
I tried debug it into the echo/bind.go the Bind function in reading the QueryParam actually read filter[status] as plain string "filter[status]" as key.
Is there anyway we can pass the map to the url query param? or we really have to be specific in here?
I am learning about the best practices involved in REST API design and wrote a function which handles the GET /cities HTTP/1.1 query.
This function contains cities which is a struct array that holds the cityname, citycode of multiple cities.
Below is the code
func FindCitiesHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
if len(cities) == 0 {
w.WriteHeader(404)
return
}
if err := json.NewEncoder(w).Encode(cities); err != nil {
/* what to do here? */
}
w.WriteHeader(200)
}
Now when I started thinking about the possible outcomes of this function. I found these situations.
It returns all the cities properly as JSON response successfully. So I return 200 http status.
The list of cities is empty. So there is nothing to return. So I return 404 (resource not found)
It is about to return JSON response of all cities, but something gone wrong during JSON encoding. Now I am confused here, how do I deal with this situation.
I mean how do you convey message properly to user, if
If your business/application logic had some error/exception.
If data access logic found some issues. (say connection to database is not reachable)
Could you guys please help me to suggest best practices you followed in these situations?
200 is correct
404 is probably not correct; A list of cities can be empty, and still exist. (Think: A 0-length array is still an array.) You should probably return 200. You would only return 404 if the list of cities doesn't exist on your server (in other words, that should probably never happen with your API).
If you experience an internal server error, such as with marshaling JSON, you should return an Internal Server Error, status 500.
I'm using huandu/facebook for Golang to access the FB API. https://github.com/huandu/facebook
This works really well locally but when I try to run from the Google App Engine environment, I can't get it to run.
I used this code locally:
res, err := fb.Get("/me", fb.Params{
"fields": "id,first_name,last_name,name",
"access_token": usertoken,
})
In the documentation (link above) they do mention the App Engine environment but I can'f figure out how to ge this to work with the fb.Get convention.
Thanks.
Edit
Almost got it to work!:
// create a global App var to hold app id and secret.
var globalApp = fb.New("<appId>", "<appSecret>")
session := globalApp.Session(usertoken) //User token here
context := appengine.NewContext(r) //Not sure what r should be...
session.HttpClient = urlfetch.Client(context)
res, err := session.Get("/me", nil)
if err := json.NewEncoder(w).Encode(res); err != nil {
panic(err)
}
If I do this I get back the Id and name. Now all I need to do is request the other parameters. Do I do this in the r parameter to the app engine context?
To answer the last question asked, the appengine.NewContext(r) function takes a *http.Request as a parameter, but this refers to the current request, the one your code is executing in. You can use r.URL.Query() if you wanted to get the query parameters that were sent to this request.
If you want to send parameters in another request, like the one to the Facebook API, you can include them directly in the URL you pass to session.Get(). You can use url.Values.Encode if you want to build a query string from a map of values. If you need to make a request using a method other than GET, such as to an API method that expects JSON, you can use http.NewRequest eg.
session.HttpClient = urlfetch.Client(context)
request, err := http.NewRequest("PUT", url, strings.NewReader("{ "someProperty": 1234 }"))
response, err := session.Do(request)
I am a novice with vertx so maybe I am doing something wrong. I am trying to implement the following routes:
router.get("/api/users/").handler(this::getUsers);
router.route("/api/users/:username*").handler(this::checkUsername);
router.get("/api/users/:username/").handler(this::getUser);
router.put("/api/users/:username/").handler(this::addUser);
router.get("/api/users/:username/assignments/").handler(this::getAssignments);
router.post("/api/users/:username/assignments/").handler(this::addAssignment);
router.route("/api/users/:username/assignments/:assignmentId/").handler(this::checkAssignmentId);
router.get("/api/users/:username/assignments/:assignmentId/").handler(this::getAssignment);
Is this the correct way to avoid duplicating this logic in all handlers?
I am trying to chain handlers, where the checkUsername handler reads the username parameter from the path, tries to find a corresponding user, and puts that user in the context. If no user is found, a statuscode 400 is returned. Otherwise the next handler is called. I would like to apply the same principle to the assignmentId parameter.
While trying to implement this, I believe I found a problem with the path, more specifically the trailing slash and star. The documentation states that trailing slashes are ignored. This is not the behavior when there is a parameter in the path. In that case the trailing slash matters. If the path definition contains one and the request does not, vertx returns a 404. It does not make a difference whether or not the parameter is at the end of the path or in the middle.
The same goes for paths ending with a star. This functionality does not work when the path contains a parameter.
You can use a regular expression to avoid duplication of the checkUsername validation check. What I would do is I would have a method like this to check if the username is valid:
private void checkUsername(RoutingContext routingContext){
//The "param0" is the capture group of the regular expression. See the routing config below.
if (isValidUsername(routingContext.request().getParam("param0"))){
routingContext.next();
} else {
routingContext
.response()
.setStatusCode(400)
.end();
}
}
To check the assignment ID I would do something similar:
private void checkAssignmentId(RoutingContext routingContext){
if (isValidAssignmentId(routingContext.request().getParam("assignmentId"))){
routingContext.next();
} else {
routingContext
.response()
.setStatusCode(400)
.end();
}
}
Try to avoid trailing slashes in your paths. I would change the routing handler assignments to be something like this:
router.get("/api/users").handler(this::getUsers);
//By the way, you really want to be using a POST request when adding users just to stick to the principles of REST.
//When you are sending a POST request there is no need to put the username in the URI. You can have it in the request body.
//Please ensure you validate this username using the same validation helper used in your other validations.
router.post("/api/users").handler(this::addUser);
//Use regular expression to match all "/api/users/:username*" URIs
router.routeWithRegex("\\/api\\/users\\/([^\\/]+)").handler(this::checkUsername);
router.get("/api/users/:username").handler(this::getUser);
router.get("/api/users/:username/assignments").handler(this::getAssignments);
router.post("/api/users/:username/assignments").handler(this::addAssignment);
router.route("/api/users/:username/assignments/:assignmentId").handler(this::checkAssignmentId);
router.get("/api/users/:username/assignments/:assignmentId").handler(this::getAssignment);