The POST request encodes the data

<! — TOC –>

  • URLSearchParams

    • Read and transform operations for URLSearchParams
    • url.searchParams
    • Let URLSearchParams be the body of the Fetch.
  • FormData

    • Let FormData be the body of the Fetch
    • Convert URLSearchParams
    • Read the body of the Fetch as FormData
  • Other formats that can be used as the body of the Fetch

    • Blobs
    • Strings
    • Buffers
    • Streams
  • One final bonus: Convert FormData to JSON
  • reference

OK, come on. Let’s start with a code example:

async function isPositive(text) {
  const response = await fetch(`http://text-processing.com/api/sentiment/`, {
    method: 'POST',
    body: `text=${text}`,
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
  });
  const json = await response.json();
  return json.label === 'pos';
}

This piece of code is poorly written and may cause a security problem. Why is that? ${text} =${text}

Unescaped text is added to a format with a defined encoding. That is, the text variable, which is written directly to the body of the request without escaping (or encoding), is’ content-type ‘: ‘application/x-www-form-urlencoded’ in this request.

This notation is somewhat similar to SQL/HTML injection, in that something intended to be a “value” (meaning something like a text variable) can interact directly with the format.

So, I’ll delve into the right way to do it, while also looking at some related, lesser-known APIs:

URLSearchParams

URLSearchParams can be used to process encoding and decoding application/x-www-form-urlencoded data. It’s very convenient because, um…

The application/x-www-form-urlencoded format is in many ways an aberrant monstrosity, the result of many years of implementation accidents and compromises leading to a set of requirements necessary for interoperability, but in no way representing good design practices. In particular, readers are cautioned to pay close attention to the twisted details involving repeated (and in some cases nested) conversions between character encodings and byte sequences. Unfortunately the format is in widespread use due to the The prevalence of HTML forms. — The URL standard

. So, yes, it is highly recommended that you encode/decode application/x-www-form-urlencoded data yourself.

Here’s how URLSearchParams works:

const searchParams = new URLSearchParams();
searchParams.set('foo', 'bar');
searchParams.set('hello', 'world');

// Logs 'foo=bar&hello=world'
console.log(searchParams.toString());

URLSearchParams This constructor can also take an array of [key, value] pairs, or an iterator that produces [key, value] pairs:

const searchParams = new URLSearchParams([
  ['foo', 'bar'],
  ['hello', 'world'],
]);

// Logs 'foo=bar&hello=world'
console.log(searchParams.toString());

Or an object:

const searchParams = new URLSearchParams({
  foo: 'bar',
  hello: 'world',
});

// Logs 'foo=bar&hello=world'
console.log(searchParams.toString());

Or a string:

const searchParams = new URLSearchParams('foo=bar&hello=world');

// Logs 'foo=bar&hello=world'
console.log(searchParams.toString());

Read and transform operations for URLSearchParams

There are many ways to read URLSearchParams and convert URLSearchParams into arrays, objects, etc., which are described in detail on the MDN.

Its iterator comes in handy if you want to process all the data in some scenarios:

const searchParams = new URLSearchParams('foo=bar&hello=world');

for (const [key, value] of searchParams) {
  console.log(key, value);
}

This also means that you can easily convert it to [key, value] pairs of arrays:

// To [['foo', 'bar'], ['hello', 'world']]
const keyValuePairs = [...searchParams];

Or use it with an API that supports iterators that generate key-value pairs, such as Object.fromentries, to convert it to an Object:

// To { foo: 'bar', hello: 'world' }
const data = Object.fromEntries(searchParams);

Note, however, that conversions to objects are sometimes lossy: that is, they may cause some value to be lost

const searchParams = new URLSearchParams([
  ['foo', 'bar'],
  ['foo', 'hello'],
]);

// Logs "foo=bar&foo=hello"
console.log(searchParams.toString());

// To { foo: 'hello' }
const data = Object.fromEntries(searchParams);

url.searchParams

There is a searchParams property on the URL object, which makes it easy to get the request parameters:

const url = new URL('https://jakearchibald.com/?foo=bar&hello=world');

// Logs 'world'
console.log(url.searchParams.get('hello'));

Unfortunately, there is no location.searchParams property on window.location.

This is because window.location is complicated by how some of its properties work across sources. Such as setting the otherWindow. Location. Href across the source work, but does not allow access to it.

However, we need to fix it so that we can easily get the request parameters from the address bar:

// Boo, undefined
location.searchParams;

const url = new URL(location.href);
// Yay, defined!
url.searchParams;

// Or:
const searchParams = new URLSearchParams(location.search);

Let URLSearchParams be the body of the Fetch.

All right, now let’s get down to business. The code in the example at the beginning of this article is problematic because it does not escape input:

const value = 'hello&world'; const badEncoding = `text=${value}`; / / 😬 Logs [[' text ', 'hello,'], [' world ', ' ']]. The console log ([... new URLSearchParams (badEncoding)]); const correctEncoding = new URLSearchParams({ text: value }); // Logs 'text=hello%26world' console.log(correctEncoding.toString());

For ease of use, the URLSearchParams object is the body that can be directly used as a Request or Response Response, so the “correct” version of the code at the beginning of the article is:

async function isPositive(text) {
  const response = await fetch(`http://text-processing.com/api/sentiment/`, {
    method: 'POST',
    body: new URLSearchParams({ text }),
  });
  const json = await response.json();
  return json.label === 'pos';
}

If you use URLSearchParams as the body, the content-type field is automatically set to application/x-www-form-urlencoded

You cannot read the body of a Request Request or Response Response as a URLSearchParams object, but we have a few ways to work around this problem…

FormData

The FormData object can represent a set of key-value data for an HTML form.

You can add data directly to the FormData object:

const formData = new FormData();
formData.set('foo', 'bar');
formData.set('hello', 'world');

The FormData object is also an iterator, so you can convert it to an array of key-value pairs or objects, just as you would with URLSearchParams. However, unlike URLSearchParams, you can read an HTML form directly as FormData:

const formElement = document.querySelector('form');
const formData = new FormData(formElement);
console.log(formData.get('username'));

This way, you can easily get the data from the form. I use this a lot, so I find it much easier than getting the data from each element individually.

Let FormData be the body of the Fetch

Similar to URLSearchParams, you can use FormData directly as the fetch body:

const formData = new FormData();
formData.set('foo', 'bar');
formData.set('hello', 'world');

fetch(url, {
  method: 'POST',
  body: formData,
});

This automatically sets the Content-Type header to Multipart/Form-Data and sends the data in this format:

const formData = new FormData();
formData.set('foo', 'bar');
formData.set('hello', 'world');

const request = new Request('', { method: 'POST', body: formData });
console.log(await request.text());

. Console. log prints out the following:

------WebKitFormBoundaryUekOXqmLphEavsu5
Content-Disposition: form-data; name="foo"

bar
------WebKitFormBoundaryUekOXqmLphEavsu5
Content-Disposition: form-data; name="hello"

world
------WebKitFormBoundaryUekOXqmLphEavsu5--

This is what the body looks like when sending data in multipart/form-data format. It is more complex than application/x-www-form-urlencoded, but it can contain file data. However, some servers cannot handle multipart/form-data, such as: Express. If you want to support multipart/form-data in Express, you’ll need to use some library to help us like Busboy or Formidable

But what if you want to send the form as application/x-www-form-urlencoded? B: well…

Convert URLSearchParams

Because the constructor URLSearchParams can accept an iterator that generates key-value pairs, and the iterator for FormData does just that, it can generate key-value pairs, you can convert FormData to URLSearchParams:

const formElement = document.querySelector('form');
const formData = new FormData(formElement);
const searchParams = new URLSearchParams(formData);

fetch(url, {
  method: 'POST',
  body: searchParams,
});

However, this conversion process throws an error if the form data contains file data. Because application/x-www-form-urlencoded will not represent file data, URLSearchParams will not, either.

Read the body of the Fetch as FormData

You can also read Request or Response objects as FormData:

const formData = await request.formData();

This method works well if the body of the Request or Response is multipart/form-data or application/x-www-form-urlencoded. It is especially useful for handling form submissions on the server.

Other formats that can be used as the body of the Fetch

There are some other formats format can be used as the body of the Fetch:

Blobs

The BLOB object (which can also be the body of the FETCH because it inherits from the BLOB) can be the body of the FETCH:

fetch(url, {
  method: 'POST',
  body: blob,
});

This automatically sets the content-type to the value of blob.type.

Strings

fetch(url, {
  method: 'POST',
  body: JSON.stringify({ hello: 'world' }),
  headers: { 'Content-Type': 'application/json' },
});

This automatically sets the Content-Type to text/plain; Charset =UTF-8, but it can be overridden, as I did above, by setting the Content-Type to application/json

Buffers

An ArrayBuffer object, and anything supported by an ArrayBuffer, such as a UINT8Array, can be used as the body of a Fetch:

Fetch (url, {method: 'POST', body: new Uint8Array([//...]]) ), headers: { 'Content-Type': 'image/png' }, });

This does not automatically set the Content-Type field, so you need to do it yourself.

Streams

Finally, the fetch body can be a stream! For Response objects, this gives the server a different development experience, and they can also be used in conjunction with a request.

So, don’t try to handle multipart/form-data or application/x-www-form-urlencoded data yourself. Let FormData and UrlSearchParams do the hard work for you!

One final bonus: Convert FormData to JSON

At present there is a problem, that is:

How do I serialize FormData to JSON without losing data?

A form can contain fields such as:

<select option>Motherland</option> <option>Taskmaster</option>... </select>

Of course, you can select multiple values, or you can have multiple inputs with the same name:

<fieldset> <legend>TV Shows</legend> <label> <input type="checkbox" name="tvShows" value="Motherland" /> Motherland </ span > </ span > < span type="checkbox" name=" Taskmaster" value="Taskmaster"... </fieldset>

The final result is a FormData object with multiple fields of the same name, as shown below:

const formData = new FormData();
formData.append('foo', 'bar');
formData.append('tvShows', 'Motherland');
formData.append('tvShows', 'Taskmaster');

As we can see in URLSearchParams, some object conversions are lossy (some properties are thrown out) :

// { foo: 'bar', tvShows: 'Taskmaster' }
const data = Object.fromEntries(formData);

There are several ways to avoid data loss and still end up serializing the FromData data into JSON.

First, convert [key, value] to array:

// [['foo', 'bar'], ['tvShows', 'Motherland'], ['tvShows', 'Taskmaster']]
const data = [...formData];

But if you want to convert to an object instead of an array, you can do this:

const data = Object.fromEntries(
  // Get a de-duped set of keys
  [...new Set(formData.keys())]
    // Map to [key, arrayOfValues]
    .map((key) => [key, formData.getAll(key)]),
);

. The data variable of the appeal code is finally:

{
  "foo": ["bar"],
  "tvShows": ["Motherland", "Taskmaster"]
}

I prefer that every value in the data be an array, even if it has only one item. This prevents a lot of branching of code on the server and simplifies validation. Although, you might prefer the PHP/Perl convention, where field names ending in [] mean “This should generate an array,” as follows:

TvShows < select multiple name = "[]" >... </select>

And let’s convert it:

const data = Object.fromEntries(
  // Get a de-duped set of keys
  [...new Set(formData.keys())].map((key) =>
    key.endsWith('[]')
      ? // Remove [] from the end and get an array of values
        [key.slice(0, -2), formData.getAll(key)]
      : // Use the key as-is and get a single value
        [key, formData.get(key)],
  ),
);

. The data variable of the appeal code is finally:

{
  "foo": "bar",
  "tvShows": ["Motherland", "Taskmaster"]
}

Note: If the form contains file data, do not try to convert the form to JSON. If this is the case
The form form contains file dataThen use
multipart/form-dataIt would be much better.

reference

  • Encoding data for POST requests

Finally, welcome to pay attention to my official account: front-end senior Joshua