Skip to content

Commit 59d710a

Browse files
author
David Koblas
committed
Handle mutlipart/form-data
1 parent 8588110 commit 59d710a

File tree

4 files changed

+405
-36
lines changed

4 files changed

+405
-36
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@ h := handler.New(&handler.Config{
3636
})
3737
```
3838

39+
### Using Multipart Form Uploads
40+
41+
This handler supports th
42+
[GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec).
43+
All file uploads will be made available as the following Scalar that you can add to your GraphQL schemas
44+
45+
```go
46+
var UploadScalar = graphql.NewScalar(graphql.ScalarConfig{
47+
Name: "Upload",
48+
ParseValue: func(value interface{}) interface{} {
49+
if v, ok := value.(*handler.MultipartFile); ok {
50+
return v
51+
}
52+
return nil
53+
},
54+
})
55+
```
56+
3957
### Details
4058

4159
The handler will accept requests with
@@ -70,6 +88,9 @@ depending on the provided `Content-Type` header.
7088
* **`application/graphql`**: The POST body will be parsed as GraphQL
7189
query string, which provides the `query` parameter.
7290

91+
* **`multipart/form-data`**: The POST body will be parsed as GraphQL
92+
query string, which provides the `operations` parameter.
93+
[GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec)
7394

7495
### Examples
7596
- [golang-graphql-playground](https://github.com/graphql-go/playground)

handler.go

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package handler
33
import (
44
"encoding/json"
55
"io/ioutil"
6+
"mime/multipart"
67
"net/http"
78
"net/url"
9+
"strconv"
810
"strings"
911

1012
"github.com/graphql-go/graphql"
@@ -13,17 +15,24 @@ import (
1315
)
1416

1517
const (
16-
ContentTypeJSON = "application/json"
17-
ContentTypeGraphQL = "application/graphql"
18-
ContentTypeFormURLEncoded = "application/x-www-form-urlencoded"
18+
ContentTypeJSON = "application/json"
19+
ContentTypeGraphQL = "application/graphql"
20+
ContentTypeFormURLEncoded = "application/x-www-form-urlencoded"
21+
ContentTypeMultipartFormData = "multipart/form-data"
1922
)
2023

24+
type MultipartFile struct {
25+
File multipart.File
26+
Header *multipart.FileHeader
27+
}
28+
2129
type Handler struct {
2230
Schema *graphql.Schema
2331
pretty bool
2432
graphiql bool
2533
playground bool
2634
rootObjectFn RootObjectFn
35+
maxMemory int64
2736
}
2837
type RequestOptions struct {
2938
Query string `json:"query" url:"query" schema:"query"`
@@ -57,7 +66,7 @@ func getFromForm(values url.Values) *RequestOptions {
5766
}
5867

5968
// RequestOptions Parses a http.Request into GraphQL request options struct
60-
func NewRequestOptions(r *http.Request) *RequestOptions {
69+
func NewRequestOptions(r *http.Request, maxMemory int64) *RequestOptions {
6170
if reqOpt := getFromForm(r.URL.Query()); reqOpt != nil {
6271
return reqOpt
6372
}
@@ -95,6 +104,89 @@ func NewRequestOptions(r *http.Request) *RequestOptions {
95104

96105
return &RequestOptions{}
97106

107+
case ContentTypeMultipartFormData:
108+
if err := r.ParseMultipartForm(maxMemory); err != nil {
109+
// fmt.Printf("Parse Multipart Failed %v", err)
110+
return &RequestOptions{}
111+
}
112+
113+
// @TODO handle array case...
114+
115+
operationsParam := r.FormValue("operations")
116+
var opts RequestOptions
117+
if err := json.Unmarshal([]byte(operationsParam), &opts); err != nil {
118+
// fmt.Printf("Parse Operations Failed %v", err)
119+
return &RequestOptions{}
120+
}
121+
122+
mapParam := r.FormValue("map")
123+
mapValues := make(map[string]([]string))
124+
if len(mapParam) != 0 {
125+
if err := json.Unmarshal([]byte(mapParam), &mapValues); err != nil {
126+
// fmt.Printf("Parse map Failed %v", err)
127+
return &RequestOptions{}
128+
}
129+
}
130+
131+
variables := opts
132+
133+
for key, value := range mapValues {
134+
for _, v := range value {
135+
if file, header, err := r.FormFile(key); err == nil {
136+
137+
// Now set the path in ther variables
138+
var node interface{} = variables
139+
140+
parts := strings.Split(v, ".")
141+
last := parts[len(parts)-1]
142+
143+
for _, vv := range parts[:len(parts)-1] {
144+
// fmt.Printf("Doing vv=%s type=%T parts=%v\n", vv, node, parts)
145+
switch node.(type) {
146+
case RequestOptions:
147+
if vv == "variables" {
148+
node = opts.Variables
149+
} else {
150+
// panic("Invalid top level tag")
151+
return &RequestOptions{}
152+
}
153+
case map[string]interface{}:
154+
node = node.(map[string]interface{})[vv]
155+
case []interface{}:
156+
if idx, err := strconv.ParseInt(vv, 10, 64); err == nil {
157+
node = node.([]interface{})[idx]
158+
} else {
159+
// panic("Unable to lookup index")
160+
return &RequestOptions{}
161+
}
162+
default:
163+
// panic(fmt.Errorf("Unknown type %T", node))
164+
return &RequestOptions{}
165+
}
166+
}
167+
168+
data := &MultipartFile{File: file, Header: header}
169+
170+
switch node.(type) {
171+
case map[string]interface{}:
172+
node.(map[string]interface{})[last] = data
173+
case []interface{}:
174+
if idx, err := strconv.ParseInt(last, 10, 64); err == nil {
175+
node.([]interface{})[idx] = data
176+
} else {
177+
// panic("Unable to lookup index")
178+
return &RequestOptions{}
179+
}
180+
default:
181+
// panic(fmt.Errorf("Unknown last type %T", node))
182+
return &RequestOptions{}
183+
}
184+
}
185+
}
186+
}
187+
188+
return &opts
189+
98190
case ContentTypeJSON:
99191
fallthrough
100192
default:
@@ -119,7 +211,7 @@ func NewRequestOptions(r *http.Request) *RequestOptions {
119211
// user-provided context.
120212
func (h *Handler) ContextHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
121213
// get query
122-
opts := NewRequestOptions(r)
214+
opts := NewRequestOptions(r, h.maxMemory)
123215

124216
// execute graphql query
125217
params := graphql.Params{
@@ -182,14 +274,15 @@ type Config struct {
182274
GraphiQL bool
183275
Playground bool
184276
RootObjectFn RootObjectFn
277+
MaxMemory int64
185278
}
186279

187280
func NewConfig() *Config {
188281
return &Config{
189-
Schema: nil,
190-
Pretty: true,
191-
GraphiQL: true,
192-
Playground: false,
282+
Schema: nil,
283+
Pretty: true,
284+
GraphiQL: true,
285+
MaxMemory: 0,
193286
}
194287
}
195288

@@ -201,11 +294,17 @@ func New(p *Config) *Handler {
201294
panic("undefined GraphQL schema")
202295
}
203296

297+
maxMemory := p.MaxMemory
298+
if maxMemory == 0 {
299+
maxMemory = 32 << 20 // 32MB
300+
}
301+
204302
return &Handler{
205303
Schema: p.Schema,
206304
pretty: p.Pretty,
207305
graphiql: p.GraphiQL,
208306
playground: p.Playground,
209307
rootObjectFn: p.RootObjectFn,
308+
maxMemory: maxMemory,
210309
}
211310
}

0 commit comments

Comments
 (0)