...

Source file src/github.com/99designs/gqlgen/graphql/handler/transport/http_form_multipart.go

Documentation: github.com/99designs/gqlgen/graphql/handler/transport

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

View as plain text