preface

Using fetch/axios often involves file uploads, as well as other requests, including some content-Types that get overwhelmed by these different types. This article will sort out when and which type to use.

Example analysis, how to upload a picture

Form submission

The form <form> is used to collect user-submitted data and send it to the server. The following code contains: file selection box (get local file), submit button (submit form control).

<form action="http://localhost:8899/react/aa" method="post" enctype="multipart/form-data"> <input type="file" Name ="image" multiple="multiple" /> <input type="submit" /> </form>Copy the code

When the user clicks the “Submit” button, each control generates a key-value pair. The key name is the name property of the control, and the key-value is the value property of the control. We use Node as the server and use koa-body to parse the file passed by post

Router.post ('/aa', async CTX => {console.log(ctx.request.files); })Copy the code

Use axios to request

Form data is sent to the server as key-value pairs, which the browser does automatically. Sometimes, however, we want to script through the process of constructing and editing form key-value pairs. Browsers natively provide FormData objects to do this.

new FormData(form)

let formdata = new FormData(form);
Copy the code

The FormData() constructor takes an optional argument to a form element. If the parameter is omitted, an empty form is represented, otherwise key-value pairs within the form elements are processed. The usual approach is to build an empty form object. FormData provides many instance methods, and we can add key-value pairs to the form through the Append method.

formdata.append(key1,value1)
formdata.append(key2,value2)
Copy the code

The following code implements file upload by submitting the formdata formdata through axios

 <input type="file" @change="onChange">
    methods:{
        onChange(event){
            const params = new FormData()
            params.append('file',event.target.files[0])
             axios.post('http://localhost:8899/react/aa',params,{
              headers:{
                'content-type':'multipart/form-data'
              }
            })
        }
    }
Copy the code

Content-type: lib/adapters/xhr.js defines the browser to use XHR:

module.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, {{url,method,data,headers}} var requestData = config.data; var requestHeaders = config.headers; // Check whether it is an instance of formData, If (utils.isformData (requestData)) {delete requestHeaders[' content-type ']; // Let the browser set it } }) }Copy the code

IsFormData implementation: FormData is the constructor of the form object, and instanceof is used to check whether the constructor’s prototype property appears on the prototype chain of the instance object.

/** * Determine if a value is a FormData * * @param {Object} val The value to test * @returns {boolean} True if value is  an FormData, otherwise false */ function isFormData(val) { return (typeof FormData ! == 'undefined') && (val instanceof FormData); }Copy the code

Set the default value for Content-Type

Lib/defaults. Js file

defaults.headers = {
  common: {
    'Accept': 'application/json, text/plain, */*'
  }
};

utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
  defaults.headers[method] = {};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
Copy the code

Determine the type of incoming data to set invalid content-Type

When a request is made using AXIos, the default transformRequest is set to change the requested data before it is sent to the server. ‘PUT’, ‘POST’, ‘PATCH’ and ‘DELETE’ are only valid for these requests.

// lib/defaults.js file transformRequest: [function transformRequest(data, headers) { normalizeHeaderName(headers, 'Accept');  normalizeHeaderName(headers, 'Content-Type');  if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.isBuffer(data) || utils.isStream(data) || utils.isFile(data) || utils.isBlob(data) ) { return data; } if (utils.isArrayBufferView(data)) { return data.buffer;  } if (utils.isURLSearchParams(data)) { setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded; charset=utf-8'); return data.toString(); } if (utils.isObject(data)) { setContentTypeIfUnset(headers, 'application/json; charset=utf-8'); return JSON.stringify(data); } return data; }],Copy the code

Generally, content-type is not required when using axios requests. If there are special cases, you can process the content-type in transformRequest:[function(data,headers){return data}]

Using fetch requests

To illustrate, we can directly simplify the encapsulation of fetch requests. For details, see the following codepen example. This is mainly to show what happens when fetch is used to upload a file and headers request is set at the same time.

fetch(api,{
    url,
    headers:{
        'content-type':'multipart/form-data'
    },
    mode:'no-cors'
})
Copy the code

When we initiated the image upload request, we got two error messages. Click on the file where the error occurred to the right of the error message

500 (Internal Server Error) The request API server reported an error,Unexpected end of inputAn error was reported when the browser ran Response.json ().Unexpected end of Input why do browsers declare Unexpected end of inputI’ll talk about it separately later, but let’s look at why the server reported an error.

The Content of the uploaded file needs to be marked by boundary. The correct content-type: mutlipart/form-data; Boundary = —– XXXX. View our current upload request:

Compare the content-Type format for the correct image upload:

Setting the Content-Type will lose the boundary parameter when using the FETCH request, soWhen uploading a file, there is no need to set the headers field, the browser will automatically generate the full Content-Type (including boundary).

After removing the headers field, the FETCH API can upload files normally!!

Codepen sample

Codepen sample

Unexpected end of input error

The fetch API is used to fetch a resource and return a response object. If we specify content-type as application/json then the server will return us a jSON-formatted string, so when we call resopNse.json () we can parse out the correct object.

fetch(api).then(response=>response.json()).then(res=>res)
Copy the code

Parse () = json.parse () = json.parse () = json.parse () = json.parse () = Unexpected end of input The following code will output this error, causing the browser to read without a word.

JSON.parse("{")
JSON.parse('[{"test": 4}')
Copy the code

Unexpected Token < in JSON at position 0 is common, and the front end continues to use Response. JSON to parse the data returned by the server. If the content-type is not returned on the server, you will get this error.

/ / the server router. Post ('/aa, async CTX = > {CTX. Body = '< div > content < / div >'})Copy the code

Parse (“

1

“) will get the same result if you want a simple simulation using json.parse (“

1

“). The following code is the same.

JSON.parse("{sd}")
// Unexpected token s in JSON at position 1
JSON.parse("d}")
// Unexpected token d in JSON at position 0
Copy the code

Json.parse () supports the following types:

JSON.parse('{}'); // {} JSON.parse('true'); // true JSON.parse('"foo"'); // "foo" JSON.parse('[1, 5, "false"]'); // [1, 5, "false"] JSON.parse('null'); // null JSON.parse('1'); / / 1Copy the code

Therefore, in order to correctly parse the return value of the server, the content-type corresponding data type should be set uniformly at the front and back ends. The response object also supports other methods

Organize the content-type

axios

Get request mode

The request mode for GET will only use application/ X-www-form-urlencoded

Axios. Get ('/user '{params: {id: 1, name:' dd 'person:' Joe '}})Copy the code

In theory will request to http://localhost:8080/user? Id =1&name= DD&person = Zhang SAN but Url encoding will be done for special characters. Url Encoding is often called Url Encoding (also known as percent-encoding) because it is so simple, Use the % percent sign followed by a two-digit character — 0123456789ABCDEF — to represent a single byte in hexadecimal form. The default character set used for Url encoding is US-ASCII. For example, a corresponding number of bytes in the US – ASCII is 0 x61, then get after Url encoding is % 61 so after encoding the actual request path into http://localhost:8080/user? id=1&name=dd&person=%E5%BC%A0%E4%B8%89

Post request mode

  1. Application/x – WWW – form – urlencoded format

    This mode is used by default. When using this mode, you need to process the incoming parameters intoname=hehe&age=10If you pass the pair as an object, the default configuration in AXIos will set the content-type to application/json; charset=utf-8

const service = axios.create({ baseURL, Headers}) {application/x-www-form-urlencoded' content-type if (isFormUrlencoded) {let list: application/x-www-form-urlencoded' content-type if (isFormUrlencoded) { string[] = [] for (let key in params) { list.push(`${key}=${encodeURIComponent(params[key])}`) } fetchResult = service({ Url, method: 'POST', // process {a:1,b=2} into a=1&b=2 and pass it to data: list.join('&') }) } else { fetchResult = service.post(url, params) }Copy the code
  1. text/plain
    Axios. Post (' http://localhost:8899/react/aa ', 'I am content, {headers: {' the content-type:' text/plain '}})Copy the code

Multipart /form-data upload file type does not need to be set, the browser will automatically add!!

4. application/json axios.post('http://localhost:8899/react/aa',{name:'dd',age:18})

fetch

Get application/ X-www-form-urlencoded

The method of get passing parameters needs to be added to the path, so the WORK of Url encoding needs to be implemented manually

Fetch ('http://localhost:8080? a=1&b=2', { method: For (let key in params){param += '${key}=${encodeURIComponent(params[key])}&'}Copy the code

Post way

  1. application/x-www-form-urlencoded
fetch('http://localhost:8080',{
    method:'POST',
    headers:{
        'content-type':'application/x-www-form-urlencoded'
    },
    mode:'no-cors',
    body:'name=dd&age=12'
})
Copy the code
  1. application/json

You need to use json.stringify () to convert the object into a JSON string, with the body as the field to accept the data

fetch(url, {
  method: 'POST', // or 'PUT'
  body: JSON.stringify(data), 
  headers: new Headers({
    'Content-Type': 'application/json'
  })
})
Copy the code
  1. Multipart /form-data upload files do not need to set this type, the browser will automatically add!!
let data = new FormData()
data.append('file',target.files[0])
fetch(url,{
    body:data,
    method:"POST",
    headers:{}
})
Copy the code

reference

Error when POST file multipart/form-data Chrome: Uncaught SyntaxError: Unexpected End of Input URL Encoding and decoding Request header Screenshot