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")), &params); 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(), &params)
   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  }