Why does `Client.Send` returns nil err when status code is != 200 - sendgrid

I am using sendgrid go library to send an email from my go server with the following snippet:
to := mail.NewEmail("", req.ToEmail)
from := mail.NewEmail("", "xxx#gmail.com") // redacted
m := mail.NewV3MailInit(from, "", to)
m.SetTemplateID("d-xxxxx") // redacted
client := sendgrid.NewSendClient(os.Getenv("SENDGRID_API_KEY"))
resp, err := client.Send(m)
if err != nil {
log.Printf("failed to send email to %s: %v\n", req.ToEmail, err)
}
However, when I didn't set the SENDGRID_API_KEY env var, the err was still nil (and the response was as following:
{
StatusCode:401
Body:{"errors":[{"message":"The provided authorization grant is invalid, expired, or revoked","field":null,"help":null}]}
Headers:map[Access-Control-Allow-Headers:[Authorization, Content-Type, On-behalf-of, x-sg-elas-acl] Access-Control-Allow-Methods:[POST] Access-Control-Allow-Origin:[https://sendgrid.api-docs.io] Access-Control-Max-Age:[600] Connection:[keep-alive] Content-Length:[116] Content-Type:[application/json] Date:[Mon, 05 Jul 2021 19:15:31 GMT] Server:[nginx] Strict-Transport-Security:[max-age=600; includeSubDomains] X-No-Cors-Reason:[https://sendgrid.com/docs/Classroom/Basics/API/cors.html]]}
I was blindly thinking that err should be set to something something non-nil, but I guess I need to look at status code in the response as well. Is there a quick way to check if the response returned is OK ?

Twilio SendGrid developer evangelist here.
I'm not a Go developer, so I might be wrong here, but I think I know what's going on in general here.
The SendGrid Go client uses the SendGrid REST client which ultimately calls on Go's standard lib HTTP client. The HTTP docs, under the Get method, say:
An error is returned if there were too many redirects or if there was an HTTP protocol error. A non-2xx response doesn't cause an error. Any returned error will be of type *url.Error. The url.Error value's Timeout method will report true if request timed out or was canceled.
An HTTP request that returns a result is a successful request, even if the result is not something you wanted. An HTTP request that doesn't return a result, like a connection error or too many redirects, will return an error.
According to the SendGrid API docs you should be looking for a 202 response status code to indicate a successful API request.

Related

Adding information to Golang Gin HTTP error responses

I am trying to return a custom error response when an HTTP 500 Internal Error is encountered. If I use c.JSON(http.StatusInternalServerError, CustomError{}) when a database write error occurs, Gin ignores my CustomError struct and substitutes a default "500 server Error" message.
How can I add information to the default response or return a custom response while still using the HTTP 500 Internal Server error code?
This is what I am trying to accomplish. Notifying users of a duplicate entry in the Mongo database. Gin ignores my DBErrorResponse struct and just returns the default 500 error json response.
_, err := handler.collection.InsertOne(handler.ctx, data)
if err != nil {
if mongo.IsDuplicateKeyError(err) {
dbErr := err.(mongo.WriteException)
c.JSON(
http.StatusInternalServerError,
models.DBErrorResponse{
Type: "Write Exception",
Code: dbErr.WriteErrors[0].Code,
Err: "similar record exists",
})
return
}
If the error is caused by a user providing a duplicate key, it's not an internal server error. You might want to use something like BadRequest(400), which suits duplicate value far more, if provided by the client. Thus, you should be able to return a custom error message with StatusCode 400.
Additionally, as far as I know, InternalServerError(500) is not designed to provide a 'server-side' problem feedback to the client, since, well, it's not public information. Although I'm not certainly sure if that's so and if is, why.
UPD: As Gavin mentioned, httpCode 409 is far better choice, here is the doc:
HTTP 409 error status: The HTTP 409 status code (Conflict) indicates that the request could not be processed because of conflict in the request, such as the requested resource is not in the expected state, or the result of processing the request would create a conflict within the resource.

How do I set HTTP status code conditionally in a Go HTTP server?

I have the following HTTP handler function:
func (h *UptimeHttpHandler) CreateSchedule(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
dec := json.NewDecoder(r.Body)
var req ScheduleRequest
if err := dec.Decode(&req); err != nil {
// error handling omited
}
result, err := saveToDb(req)
if err != nil {
// error handling omited
}
// Responding with 201-Created instead of default 200-Ok
w.WriteHeader(http.StatusCreated)
enc := json.NewEncoder(w)
if err := enc.Encode(result); err != nil {
// this will have no effect as the status is already set before
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "%v", err)
}
}
Above code does the following:
Request comes as a JSON data. It is decoded into req
Request is persisted in the DB. This returns a result object which will be the response
Now here, as soon as the DB insert is successful, we set the status code to 201. Then use a JSON encoder to stream the JSON encoded value directly to ResponseWriter.
When encode returns an error, I need to change the status code to 500. But currently I can't do it as Go allows to set the status code only once.
I can handle this by keeping the encoded JSON in memory, and setting the status code only when it is success. But then this creates unwanted copies and not really nice.
Is there a better way to handle this?
Expanding my comment a bit:
There is no way to do this without buffering. And if you think about it, even if there was, how would that be implemented? The response header needs to be sent before the contents. If the code depends on encoding the response, you must first encode, check the result, set the header and flush the encoded buffer.
So even if the Go API supported a "delayed" status, you would be basically pushing the buffering problem one level down, to the http library :)
You should make the call - either it's important to get the response code right at all costs and you can afford to buffer, or you want to stream the response and you cannot change the result.
Theoretically Go could create an encoding validator that will make sure the object you are trying to encode will 100% pass before actually encoding.
BTW, this makes me think of another thing - the semantic meaning of the response code. You return HTTP Created, right? It's the correct code since the object has in fact been created. But if you return a 500 error due to encoding, has the object not been created? It still has! So the Created code is still valid, the error is just in the encoding stage. So maybe a better design would be not to return the object as a response?
The JSON marshalling may fail if some of your types (deeply) can not be serialized to JSON.
So you should ask yourself: what are the case where the encoding of the response can fail?
If you know the answer, just fix those cases. If you don't know, this is probably that those cases just don't exist.
If you are not sure, the only way to catch errors is to buffer the encoding. You have to balance the "not nice" between buffering and not returning a clean error.
Anyway, returning a 500 error if just the JSON encoding failed is really not nice. At least, you should revert the object creation (the work of saveToDb), so you have to wrap in a transaction that you will rollback in any failure cases.

How can I execute a GET request with a body in Go?

I am trying to write a client for an internal API endpoint that accepts a GET request with a JSON body:
curl -X GET -d '{"foo":"bar"}' <my api>
Since I can't change the API to accept a POST, I assumed implementing the same thing in Go would be trivial, but I haven't been able to get this to work.
I've tried to model my attempt after this example of a URL encoded POST request but not sure how I would alter this to work with a GET request.
Thanks in advance
This ended up working:
stmt := `{"foo": "bar"}`
req, err := http.NewRequest("GET", "<api>", bytes.NewBuffer([]byte(stmt)))
resp, _ := client.Do(r)

ETags and 304 Not Modified response in grpc REST gateway

I am auto-generating a REST API from a gRPC spec using the grpc-gateway project from Github. In this REST API, I'd like to support ETag headers and 304 Not Modified responses.
As far as I understand, normally you would create a response in the gRPC server with a specific status code, and that status code will then get translated into an HTTP status code by the grpc-gateway. However, since standard gRPC doesn't really support caching concepts, there is no gRPC status code that maps to the HTTP 304 status code.
Using grpc-gateway, it seems to be possible to customise HTTP status codes whenever the gRPC status code is en error code (overwriting the runtime.HTTPError function). However, I haven't found any way to customise the HTTP response code when the gRPC repsonse code is OK.
So, are there any recommended ways of achieving this?
Here's an example to implement basic etag and 304 responses using a custom forwarder.
You can refer to these directions to get setup, then implement the method along the lines of:
func forwardGetPost(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
// add cache-control rules for this proxy endpoint
w.Header().Add("Cache-Control", "max-age=60")
// create an etag
// (when the response represents some entity from the db it may have a last edited timestamp)
p := resp.(*Post)
etag := fmt.Sprintf("W/%q", p.GetLastEdited())
w.Header().Add("ETag", etag)
// check whether the request provides an etag
inm := req.Header.Get("If-None-Match")
if inm != "" {
if inm == etag {
w.WriteHeader(http.StatusNotModified)
}
}
runtime.ForwardResponseMessage(ctx, mux, marshaler, w, req, resp, opts...)
}
This does not prevent the proxy from making a request to the grpc server, but it will prevent sending those bytes back to the client when etags match.

Wikipedia url stopped after 10 redirects error GoLang

Upon executing an HTTP Get request, I receive the following error:
2015/08/30 16:42:09 Get https://en.wikipedia.org/wiki/List_of_S%26P_500_companies:
stopped after 10 redirects
In the following code:
package main
import (
"net/http"
"log"
)
func main() {
response, err := http.Get("https://en.wikipedia.org/wiki/List_of_S%26P_500_companies")
if err != nil {
log.Fatal(err)
}
}
I know that according to the documentation,
// Get issues a GET to the specified URL. If the response is one of
// the following redirect codes, Get follows the redirect, up to a
// maximum of 10 redirects:
//
// 301 (Moved Permanently)
// 302 (Found)
// 303 (See Other)
// 307 (Temporary Redirect)
//
// An error is returned if there were too many redirects or if there
// was an HTTP protocol error. A non-2xx response doesn't cause an
// error.
I was hoping that somebody knows what the solution would be in this case. It seems rather odd that this simple url results in more than ten redirects. Makes me think that there may be more going on behind the scenes.
Thank you.
As others have pointed out, you should first give thought to why you are encountering so many HTTP redirects. Go's default policy of stopping at 10 redirects is reasonable. More than 10 redirects could mean you are in a redirect loop. That could be caused outside your code. It could be induced by something about your network configuration, proxy servers between you and the website, etc.
That said, if you really do need to change the default policy, you do not need to resort to editing the net/http source as someone suggested.
To change the default handling of redirects you will need to create a Client and set CheckRedirect.
For your reference:
http://golang.org/pkg/net/http/#Client
// If CheckRedirect is nil, the Client uses its default policy,
// which is to stop after 10 consecutive requests.
CheckRedirect func(req *Request, via []*Request) error
I had this issue with Wikipedia URLs containing %26 because they redirect to a version of the URL with & which Go then encodes to %26 which Wikipedia redirects to & and ...
Oddly, removing gcc-go (v1.4) from my Arch box and replacing it with go (v1.5) has fixed the problem.
I'm guessing this can be put down to the changes in net/http between v1.4 and v1.5 then.