github.com/fortexxx/gqlgen@v0.10.3-0.20191216030626-ca5ea8b21ead/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 var err error 60 if r.ContentLength > f.maxUploadSize() { 61 writeJsonError(w, "failed to parse multipart form, request body too large") 62 return 63 } 64 r.Body = http.MaxBytesReader(w, r.Body, f.maxUploadSize()) 65 if err = r.ParseMultipartForm(f.maxUploadSize()); err != nil { 66 w.WriteHeader(http.StatusUnprocessableEntity) 67 if strings.Contains(err.Error(), "request body too large") { 68 writeJsonError(w, "failed to parse multipart form, request body too large") 69 return 70 } 71 writeJsonError(w, "failed to parse multipart form") 72 return 73 } 74 defer r.Body.Close() 75 76 var params graphql.RawParams 77 78 if err = jsonDecode(strings.NewReader(r.Form.Get("operations")), ¶ms); err != nil { 79 w.WriteHeader(http.StatusUnprocessableEntity) 80 writeJsonError(w, "operations form field could not be decoded") 81 return 82 } 83 84 var uploadsMap = map[string][]string{} 85 if err = json.Unmarshal([]byte(r.Form.Get("map")), &uploadsMap); err != nil { 86 w.WriteHeader(http.StatusUnprocessableEntity) 87 writeJsonError(w, "map form field could not be decoded") 88 return 89 } 90 91 var upload graphql.Upload 92 for key, paths := range uploadsMap { 93 if len(paths) == 0 { 94 w.WriteHeader(http.StatusUnprocessableEntity) 95 writeJsonErrorf(w, "invalid empty operations paths list for key %s", key) 96 return 97 } 98 file, header, err := r.FormFile(key) 99 if err != nil { 100 w.WriteHeader(http.StatusUnprocessableEntity) 101 writeJsonErrorf(w, "failed to get key %s from form", key) 102 return 103 } 104 defer file.Close() 105 106 if len(paths) == 1 { 107 upload = graphql.Upload{ 108 File: file, 109 Size: header.Size, 110 Filename: header.Filename, 111 } 112 113 if err := params.AddUpload(upload, key, paths[0]); err != nil { 114 w.WriteHeader(http.StatusUnprocessableEntity) 115 writeJsonGraphqlError(w, err) 116 return 117 } 118 } else { 119 if r.ContentLength < f.maxMemory() { 120 fileBytes, err := ioutil.ReadAll(file) 121 if err != nil { 122 w.WriteHeader(http.StatusUnprocessableEntity) 123 writeJsonErrorf(w, "failed to read file for key %s", key) 124 return 125 } 126 for _, path := range paths { 127 upload = graphql.Upload{ 128 File: &bytesReader{s: &fileBytes, i: 0, prevRune: -1}, 129 Size: header.Size, 130 Filename: header.Filename, 131 } 132 133 if err := params.AddUpload(upload, key, path); err != nil { 134 w.WriteHeader(http.StatusUnprocessableEntity) 135 writeJsonGraphqlError(w, err) 136 return 137 } 138 } 139 } else { 140 tmpFile, err := ioutil.TempFile(os.TempDir(), "gqlgen-") 141 if err != nil { 142 w.WriteHeader(http.StatusUnprocessableEntity) 143 writeJsonErrorf(w, "failed to create temp file for key %s", key) 144 return 145 } 146 tmpName := tmpFile.Name() 147 defer func() { 148 _ = os.Remove(tmpName) 149 }() 150 _, err = io.Copy(tmpFile, file) 151 if err != nil { 152 w.WriteHeader(http.StatusUnprocessableEntity) 153 if err := tmpFile.Close(); err != nil { 154 writeJsonErrorf(w, "failed to copy to temp file and close temp file for key %s", key) 155 return 156 } 157 writeJsonErrorf(w, "failed to copy to temp file for key %s", key) 158 return 159 } 160 if err := tmpFile.Close(); err != nil { 161 w.WriteHeader(http.StatusUnprocessableEntity) 162 writeJsonErrorf(w, "failed to close temp file for key %s", key) 163 return 164 } 165 for _, path := range paths { 166 pathTmpFile, err := os.Open(tmpName) 167 if err != nil { 168 w.WriteHeader(http.StatusUnprocessableEntity) 169 writeJsonErrorf(w, "failed to open temp file for key %s", key) 170 return 171 } 172 defer pathTmpFile.Close() 173 upload = graphql.Upload{ 174 File: pathTmpFile, 175 Size: header.Size, 176 Filename: header.Filename, 177 } 178 179 if err := params.AddUpload(upload, key, path); err != nil { 180 w.WriteHeader(http.StatusUnprocessableEntity) 181 writeJsonGraphqlError(w, err) 182 return 183 } 184 } 185 } 186 } 187 } 188 189 rc, gerr := exec.CreateOperationContext(r.Context(), ¶ms) 190 if gerr != nil { 191 resp := exec.DispatchError(graphql.WithOperationContext(r.Context(), rc), gerr) 192 w.WriteHeader(http.StatusUnprocessableEntity) 193 writeJson(w, resp) 194 return 195 } 196 responses, ctx := exec.DispatchOperation(r.Context(), rc) 197 writeJson(w, responses(ctx)) 198 }