github.com/maeglindeveloper/gqlgen@v0.13.1-0.20210413081235-57808b12a0a0/graphql/handler/transport/http_form.go (about) 1 package transport 2 3 import ( 4 "encoding/json" 5 "io" 6 "io/ioutil" 7 "mime" 8 "net/http" 9 "os" 10 "strings" 11 12 "github.com/99designs/gqlgen/graphql" 13 ) 14 15 // MultipartForm the Multipart request spec https://github.com/jaydenseric/graphql-multipart-request-spec 16 type MultipartForm struct { 17 // MaxUploadSize sets the maximum number of bytes used to parse a request body 18 // as multipart/form-data. 19 MaxUploadSize int64 20 21 // MaxMemory defines the maximum number of bytes used to parse a request body 22 // as multipart/form-data in memory, with the remainder stored on disk in 23 // temporary files. 24 MaxMemory int64 25 } 26 27 var _ graphql.Transport = MultipartForm{} 28 29 func (f MultipartForm) Supports(r *http.Request) bool { 30 if r.Header.Get("Upgrade") != "" { 31 return false 32 } 33 34 mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) 35 if err != nil { 36 return false 37 } 38 39 return r.Method == "POST" && mediaType == "multipart/form-data" 40 } 41 42 func (f MultipartForm) maxUploadSize() int64 { 43 if f.MaxUploadSize == 0 { 44 return 32 << 20 45 } 46 return f.MaxUploadSize 47 } 48 49 func (f MultipartForm) maxMemory() int64 { 50 if f.MaxMemory == 0 { 51 return 32 << 20 52 } 53 return f.MaxMemory 54 } 55 56 func (f MultipartForm) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) { 57 w.Header().Set("Content-Type", "application/json") 58 59 start := graphql.Now() 60 61 var err error 62 if r.ContentLength > f.maxUploadSize() { 63 writeJsonError(w, "failed to parse multipart form, request body too large") 64 return 65 } 66 r.Body = http.MaxBytesReader(w, r.Body, f.maxUploadSize()) 67 if err = r.ParseMultipartForm(f.maxMemory()); err != nil { 68 w.WriteHeader(http.StatusUnprocessableEntity) 69 if strings.Contains(err.Error(), "request body too large") { 70 writeJsonError(w, "failed to parse multipart form, request body too large") 71 return 72 } 73 writeJsonError(w, "failed to parse multipart form") 74 return 75 } 76 defer r.Body.Close() 77 78 var params graphql.RawParams 79 80 if err = jsonDecode(strings.NewReader(r.Form.Get("operations")), ¶ms); err != nil { 81 w.WriteHeader(http.StatusUnprocessableEntity) 82 writeJsonError(w, "operations form field could not be decoded") 83 return 84 } 85 86 var uploadsMap = map[string][]string{} 87 if err = json.Unmarshal([]byte(r.Form.Get("map")), &uploadsMap); err != nil { 88 w.WriteHeader(http.StatusUnprocessableEntity) 89 writeJsonError(w, "map form field could not be decoded") 90 return 91 } 92 93 var upload graphql.Upload 94 for key, paths := range uploadsMap { 95 if len(paths) == 0 { 96 w.WriteHeader(http.StatusUnprocessableEntity) 97 writeJsonErrorf(w, "invalid empty operations paths list for key %s", key) 98 return 99 } 100 file, header, err := r.FormFile(key) 101 if err != nil { 102 w.WriteHeader(http.StatusUnprocessableEntity) 103 writeJsonErrorf(w, "failed to get key %s from form", key) 104 return 105 } 106 defer file.Close() 107 108 if len(paths) == 1 { 109 upload = graphql.Upload{ 110 File: file, 111 Size: header.Size, 112 Filename: header.Filename, 113 ContentType: header.Header.Get("Content-Type"), 114 } 115 116 if err := params.AddUpload(upload, key, paths[0]); err != nil { 117 w.WriteHeader(http.StatusUnprocessableEntity) 118 writeJsonGraphqlError(w, err) 119 return 120 } 121 } else { 122 if r.ContentLength < f.maxMemory() { 123 fileBytes, err := ioutil.ReadAll(file) 124 if err != nil { 125 w.WriteHeader(http.StatusUnprocessableEntity) 126 writeJsonErrorf(w, "failed to read file for key %s", key) 127 return 128 } 129 for _, path := range paths { 130 upload = graphql.Upload{ 131 File: &bytesReader{s: &fileBytes, i: 0, prevRune: -1}, 132 Size: header.Size, 133 Filename: header.Filename, 134 ContentType: header.Header.Get("Content-Type"), 135 } 136 137 if err := params.AddUpload(upload, key, path); err != nil { 138 w.WriteHeader(http.StatusUnprocessableEntity) 139 writeJsonGraphqlError(w, err) 140 return 141 } 142 } 143 } else { 144 tmpFile, err := ioutil.TempFile(os.TempDir(), "gqlgen-") 145 if err != nil { 146 w.WriteHeader(http.StatusUnprocessableEntity) 147 writeJsonErrorf(w, "failed to create temp file for key %s", key) 148 return 149 } 150 tmpName := tmpFile.Name() 151 defer func() { 152 _ = os.Remove(tmpName) 153 }() 154 _, err = io.Copy(tmpFile, file) 155 if err != nil { 156 w.WriteHeader(http.StatusUnprocessableEntity) 157 if err := tmpFile.Close(); err != nil { 158 writeJsonErrorf(w, "failed to copy to temp file and close temp file for key %s", key) 159 return 160 } 161 writeJsonErrorf(w, "failed to copy to temp file for key %s", key) 162 return 163 } 164 if err := tmpFile.Close(); err != nil { 165 w.WriteHeader(http.StatusUnprocessableEntity) 166 writeJsonErrorf(w, "failed to close temp file for key %s", key) 167 return 168 } 169 for _, path := range paths { 170 pathTmpFile, err := os.Open(tmpName) 171 if err != nil { 172 w.WriteHeader(http.StatusUnprocessableEntity) 173 writeJsonErrorf(w, "failed to open temp file for key %s", key) 174 return 175 } 176 defer pathTmpFile.Close() 177 upload = graphql.Upload{ 178 File: pathTmpFile, 179 Size: header.Size, 180 Filename: header.Filename, 181 ContentType: header.Header.Get("Content-Type"), 182 } 183 184 if err := params.AddUpload(upload, key, path); err != nil { 185 w.WriteHeader(http.StatusUnprocessableEntity) 186 writeJsonGraphqlError(w, err) 187 return 188 } 189 } 190 } 191 } 192 } 193 194 params.ReadTime = graphql.TraceTiming{ 195 Start: start, 196 End: graphql.Now(), 197 } 198 199 rc, gerr := exec.CreateOperationContext(r.Context(), ¶ms) 200 if gerr != nil { 201 resp := exec.DispatchError(graphql.WithOperationContext(r.Context(), rc), gerr) 202 w.WriteHeader(statusFor(gerr)) 203 writeJson(w, resp) 204 return 205 } 206 responses, ctx := exec.DispatchOperation(r.Context(), rc) 207 writeJson(w, responses(ctx)) 208 }