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