-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add multipart/form-data transport to support file uploads #268
Conversation
There is a problem with logging. Line 58 in 3dc62ad
breaks tests in instrumentation_test.go :However, code from the head: Line 55 in 949badb
just logs the whole requests body with log level info. Which is kinda not ok in case of large files (it even caused me some OOMs). For now I've just reverted my code with changes to log level, but it is an issue to address. |
Hi @benzolium, This looks cool, I'll give it a test when I have a chance. Is it possible you could add an example server to the |
@pkqk sure. Done. I've also added one more test case and updated docs with |
There’s still a problem with logging the whole body. For large files it will be a big issue. @pkqk i cannot just remove logging of body completely because there is some instrumentation logic relying on that (there are even tests for having body in logs). So what should I do? Remove body from logging by checking content type header? |
It's probably worth removing the request body from the logging by default, it was in there initially as it was useful during early development. For now you could stop it logging the body on a diff --git a/middleware.go b/middleware.go
index a370351..f74f874 100644
--- a/middleware.go
+++ b/middleware.go
@@ -104,15 +104,20 @@ func addRequestBody(e *event, r *http.Request, buf bytes.Buffer) {
contentType := r.Header.Get("Content-Type")
e.addField("request.content-type", contentType)
- if r.Method != http.MethodHead &&
- r.Method != http.MethodGet &&
- contentType == "application/json" {
- var payload interface{}
- if err := json.Unmarshal(buf.Bytes(), &payload); err == nil {
- e.addField("request.body", &payload)
- } else {
+ if r.Method != http.MethodHead && r.Method != http.MethodGet {
+ switch contentType {
+ case "application/json":
+ var payload interface{}
+ if err := json.Unmarshal(buf.Bytes(), &payload); err == nil {
+ e.addField("request.body", &payload)
+ } else {
+ e.addField("request.body", buf.String())
+ e.addField("request.error", err)
+ }
+ case "multipart/form-data":
+ e.addField("request.body", fmt.Sprintf("%d bytes", len(buf.Bytes())))
+ default:
e.addField("request.body", buf.String())
- e.addField("request.error", err)
}
} else {
e.addField("request.body", buf.String()) |
@benzolium I tried testing the example service but I'm getting these errors {
"errors": [
{
"message": "unexpected response code: 422 Unprocessable Entity",
"locations": [
{
"line": 2,
"column": 2
},
{
"line": 3,
"column": 2
}
],
"extensions": {
"selectionSet": "{ uploadGizmoFile(upload: $fileA) uploadGadgetFile(upload: {upload:$fileB}) }"
}
}
],
"data": null
} This is the curl command I'm using curl --url http://localhost:8082/query \
--form 'operations={"query":"mutation upload($fileA: Upload!, $fileB: Upload!) {\n\tuploadGizmoFile(upload: $fileA)\n\tuploadGadgetFile(upload: {\n\t\tupload: $fileB\n\t})\n}","variables":{"fileA":null,"fileB":null}}' \
--form 'map={
"a": ["variables.fileA"],
"b": ["variables.fileB"]
}' \
--form [email protected] \
--form [email protected] |
@pkqk good catch. Fixed. I was populating files map incorrectly. |
The named structs don't serve a lot of purpose when they are only used in one location. The variables map is being modified in place which is not very visible. This can lead to unexpected bugs.
Bring the stack helper struct definition into the function Remove the unused key field and preset the path to start with "variables".
@benzolium I had a bunch more suggestions that were a bit too big to do through the review system. If you'd like to look at my fork Also looking through the spec examples, I noticed the current file map approach won't handle arrays of Files, i.e making a file map with |
Instead of always doing so if the upstream request was. Only services taking an upload need a multipart request, as we can't guarantee all services support the mulitpart transport.
CreateFormFile sets it to application/octet-stream without the option of specifying it, so if the upstream payload had a more specific type that would be lost.
@pkqk thanks for suggestions! I'd definitely apply some of them. However there are several things I'd like to discuss:
Should we open a PR to my fork and discuss suggestions there? Or maybe I should apply other suggestions and we could discuss remaining here? As for array files, I'll look into that. Haven't ever used files array so overlooked. |
Hi @benzolium, good questions, performance wise, we are dealing with a lot of json parsing and serialisation which is going to be an order of magnitude slower than any of the checks we do here, so I wouldn't consider optimising these until it proves to be an issue.
In general I'd go for clarity and slightly less efficient code than optimising before it's necessary. |
@benzolium I can either PR into your branch, or you can cherry pick the ones that you agree with and discuss the other ones here. |
Hey @pkqk! Thanks for the answers and for the ping. You can surely PR into my branch. Sorry for the delay. I’m a little but overwhelmed with work now. |
There's no rush @benzolium, looking forward to merging this in but I want us to get it polished and working well before we do. |
Multipart suggestions
Hey @pkqk! I've merged your suggestions and added a case for list of files. |
Resolves #154
@pkqk this a draft. Please take a look. Any suggestions or remarks?
I'm currently testing it manually.
Though I'm using almost the same approach for 6 month on production. 🙂