Sending form data
Once the form data has been validated on the client-side, it is okay to submit the form. And, since we covered validation in the previous article, we’re ready to submit! This article looks at what happens when a user submits a form — where does the data go, and how do we handle it when it gets there? We also look at some of the security concerns associated with sending form data.
| Prerequisites: | Basic computer literacy, an understanding of HTML, and basic knowledge of HTTP and server-side programming. |
|---|---|
| Objective: | To understand what happens when form data is submitted, including getting a basic idea of how data is processed on the server. |
First, we’ll discuss what happens to the data when a form is submitted.
Client/server architecture
At its most basic, the web uses a client/server architecture that can be summarized as follows: a client (usually a web browser) sends a request to a server (most of the time a web server like Apache, Nginx, IIS, Tomcat, etc.), using the HTTP protocol. The server answers the request using the same protocol.
An HTML form on a web page is nothing more than a convenient user-friendly way to configure an HTTP request to send data to a server. This enables the user to provide information to be delivered in the HTTP request.
Note: To get a better idea of how client-server architectures work, read our Server-side website programming first steps module.
On the client side: defining how to send the data
The <form> element defines how the data will be sent. All of its attributes are designed to let you configure the request to be sent when a user hits a submit button. The two most important attributes are action and method .
The action attribute
The action attribute defines where the data gets sent. Its value must be a valid relative or absolute URL. If this attribute isn’t provided, the data will be sent to the URL of the page containing the form — the current page.
In this example, the data is sent to an absolute URL — https://example.com :
Here, we use a relative URL — the data is sent to a different URL on the same origin:
When specified with no attributes, as below, the <form> data is sent to the same page that the form is present on:
Note: It’s possible to specify a URL that uses the HTTPS (secure HTTP) protocol. When you do this, the data is encrypted along with the rest of the request, even if the form itself is hosted on an insecure page accessed using HTTP. On the other hand, if the form is hosted on a secure page but you specify an insecure HTTP URL with the action attribute, all browsers display a security warning to the user each time they try to send data because the data will not be encrypted.
The names and values of the non-file form controls are sent to the server as name=value pairs joined with ampersands. The action value should be a file on the server that can handle the incoming data, including ensuring server-side validation. The server then responds, generally handling the data and loading the URL defined by the action attribute, causing a new page load (or a refresh of the existing page, if the action points to the same page).
How the data is sent depends on the method attribute.
The method attribute
The method attribute defines how data is sent. The HTTP protocol provides several ways to perform a request; HTML form data can be transmitted via a number of different methods, the most common being the GET method and the POST method
To understand the difference between those two methods, let’s step back and examine how HTTP works. Each time you want to reach a resource on the Web, the browser sends a request to a URL. An HTTP request consists of two parts: a header that contains a set of global metadata about the browser’s capabilities, and a body that can contain information necessary for the server to process the specific request.
The GET method
The GET method is the method used by the browser to ask the server to send back a given resource: «Hey server, I want to get this resource.» In this case, the browser sends an empty body. Because the body is empty, if a form is sent using this method the data sent to the server is appended to the URL.
Consider the following form:
Since the GET method has been used, you’ll see the URL www.foo.com/?say=Hi&to=Mom appear in the browser address bar when you submit the form.

The data is appended to the URL as a series of name/value pairs. After the URL web address has ended, we include a question mark ( ? ) followed by the name/value pairs, each one separated by an ampersand ( & ). In this case, we are passing two pieces of data to the server:
- say , which has a value of Hi
- to , which has a value of Mom
The HTTP request looks like this:
Note: You can find this example on GitHub — see get-method.html (see it live also).
The POST method
The POST method is a little different. It’s the method the browser uses to talk to the server when asking for a response that takes into account the data provided in the body of the HTTP request: «Hey server, take a look at this data and send me back an appropriate result.» If a form is sent using this method, the data is appended to the body of the HTTP request.
Let’s look at an example — this is the same form we looked at in the GET section above, but with the method attribute set to POST .
When the form is submitted using the POST method, you get no data appended to the URL, and the HTTP request looks like so, with the data included in the request body instead:
The Content-Length header indicates the size of the body, and the Content-Type header indicates the type of resource sent to the server. We’ll discuss these headers later on.
Note: You can find this example on GitHub — see post-method.html (see it live also).
Viewing HTTP requests
HTTP requests are never displayed to the user (if you want to see them, you need to use tools such as the Firefox Network Monitor or the Chrome Developer Tools). As an example, your form data will be shown as follows in the Chrome Network tab. After submitting the form:
- Open the developer tools.
- Select «Network»
- Select «All»
- Select «foo.com» in the «Name» tab
- Select «Headers»
You can then get the form data, as shown in the image below.

The only thing displayed to the user is the URL called. As we mentioned above, with a GET request the user will see the data in their URL bar, but with a POST request they won’t. This can be very important for two reasons:
- If you need to send a password (or any other sensitive piece of data), never use the GET method or you risk displaying it in the URL bar, which would be very insecure.
- If you need to send a large amount of data, the POST method is preferred because some browsers limit the sizes of URLs. In addition, many servers limit the length of URLs they accept.
On the server side: retrieving the data
Whichever HTTP method you choose, the server receives a string that will be parsed in order to get the data as a list of key/value pairs. The way you access this list depends on the development platform you use and on any specific frameworks you may be using with it.
Example: Raw PHP
PHP offers some global objects to access the data. Assuming you’ve used the POST method, the following example just takes the data and displays it to the user. Of course, what you do with the data is up to you. You might display it, store it in a database, send it by email, or process it in some other way.
This example displays a page with the data we sent. You can see this in action in our example php-example.html file — which contains the same example form as we saw before, with a method of POST and an action of php-example.php . When it is submitted, it sends the form data to php-example.php, which contains the PHP code seen in the above block. When this code is executed, the output in the browser is Hi Mom .

Note: This example won’t work when you load it into a browser locally — browsers cannot interpret PHP code, so when the form is submitted the browser will just offer to download the PHP file for you. To get it to work, you need to run the example through a PHP server of some kind. Good options for local PHP testing are MAMP (Mac and Windows) and AMPPS (Mac, Windows, Linux).
Note also that if you are using MAMP but don’t have MAMP Pro installed (or if the MAMP Pro demo time trial has expired), you might have trouble getting it working. To get it working again, we have found that you can load up the MAMP app, then choose the menu options MAMP > Preferences > PHP, and set «Standard Version:» to «7.2.x» (x will differ depending on what version you have installed).
Example: Python
This example shows how you would use Python to do the same thing — display the submitted data on a web page. This uses the Flask framework for rendering the templates, handling the form data submission, etc. (see python-example.py).
The two templates referenced in the above code are as follows (these need to be in a subdirectory called templates in the same directory as the python-example.py file, if you try to run the example yourself):
-
: The same form as we saw above in The POST method section but with the action set to << url_for('hello') >> . This is a Jinja template, which is basically HTML but can contain calls to the Python code that is running the web server contained in curly braces. url_for(‘hello’) is basically saying «redirect to /hello when the form is submitted». : This template just contains a line that renders the two bits of data passed to it when it is rendered. This is done via the hello() function seen above, which runs when the /hello URL is navigated to.
Note: Again, this code won’t work if you just try to load it into a browser directly. Python works a bit differently from PHP — to run this code locally you’ll need to install Python/PIP, then install Flask using pip3 install flask . At this point, you should be able to run the example using python3 python-example.py , then navigate to localhost:5042 in your browser.
Other languages and frameworks
There are many other server-side technologies you can use for form handling, including Perl, Java, .Net, Ruby, etc. Just pick the one you like best. That said, it’s worth noting that it’s very uncommon to use these technologies directly because this can be tricky. It’s more common to use one of the many high quality frameworks that make handling forms easier, such as:
-
for Python (a bit more heavyweight than Flask, but with more tools and options). for Node.js. for PHP. for Ruby. for Java.
It’s worth noting that even using these frameworks, working with forms isn’t necessarily easy. But it’s much easier than trying to write all the functionality yourself from scratch, and will save you a lot of time.
Note: It is beyond the scope of this article to teach you any server-side languages or frameworks. The links above will give you some help, should you wish to learn them.
A special case: sending files
Sending files with HTML forms is a special case. Files are binary data — or considered as such — whereas all other data is text data. Because HTTP is a text protocol, there are special requirements for handling binary data.
The enctype attribute
This attribute lets you specify the value of the Content-Type HTTP header included in the request generated when the form is submitted. This header is very important because it tells the server what kind of data is being sent. By default, its value is application/x-www-form-urlencoded . In human terms, this means: «This is form data that has been encoded into URL parameters.»
If you want to send files, you need to take three extra steps:
- Set the method attribute to POST because file content can’t be put inside URL parameters.
- Set the value of enctype to multipart/form-data because the data will be split into multiple parts, one for each file plus one for the text data included in the form body (if the text is also entered into the form).
- Include one or more <input type=»file»> controls to allow your users to select the file(s) that will be uploaded.
Note: Servers can be configured with a size limit for files and HTTP requests in order to prevent abuse.
Security issues
Each time you send data to a server, you need to consider security. HTML forms are by far the most common server attack vectors (places where attacks can occur). The problems never come from the HTML forms themselves — they come from how the server handles data.
The Website security article of our server-side learning topic discusses several common attacks and potential defenses against them in detail. You should go and check that article out, to get an idea of what’s possible.
Be paranoid: Never trust your users
So, how do you fight these threats? This is a topic far beyond this guide, but there are a few rules to keep in mind. The most important rule is: never ever trust your users, including yourself; even a trusted user could have been hijacked.
All data that comes to your server must be checked and sanitized. Always. No exception.
- Escape potentially dangerous characters. The specific characters you should be cautious with vary depending on the context in which the data is used and the server platform you employ, but all server-side languages have functions for this. Things to watch out for are character sequences that look like executable code (such as JavaScript or SQL commands).
- Limit the incoming amount of data to allow only what’s necessary.
- Sandbox uploaded files. Store them on a different server and allow access to the file only through a different subdomain or even better through a completely different domain.
You should be able to avoid many/most problems if you follow these three rules, but it’s always a good idea to get a security review performed by a competent third party. Don’t assume that you’ve seen all the possible problems.
Summary
As we’d alluded to above, sending form data is easy, but securing an application can be tricky. Just remember that a front-end developer is not the one who should define the security model of the data. It’s possible to perform client-side form validation, but the server can’t trust this validation because it has no way to truly know what has really happened on the client-side.
If you’ve worked your way through these tutorials in order, you now know how to markup and style a form, do client-side validation, and have some idea about submitting a form.
See also
If you want to learn more about securing a web application, you can dig into these resources:
XMLHttpRequest POST, формы и кодировка
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/xmlhttprequest.
Во время обычной отправки формы <form> браузер собирает значения её полей, делает из них строку и составляет тело GET/POST-запроса для посылки на сервер.
При отправке данных через XMLHttpRequest , это нужно делать самим, в JS-коде. Большинство проблем и вопросов здесь связано с непониманием, где и какое кодирование нужно осуществлять.
Кодировка urlencoded
Основной способ кодировки запросов – это urlencoded, то есть – стандартное кодирование URL.
Здесь есть два поля: name=Ivan и surname=Ivanov .
Браузер перечисляет такие пары «имя=значение» через символ амперсанда & и, так как метод GET, итоговый запрос выглядит как /submit?name=Ivan&surname=Ivanov .
Все символы, кроме английских букв, цифр и — _ . !
* ‘ ( ) заменяются на их цифровой код в UTF-8 со знаком %.
Например, пробел заменяется на %20 , символ / на %2F , русские буквы кодируются двумя байтами в UTF-8, поэтому, к примеру, Ц заменится на %D0%A6 .
Будет отправлена так: /submit?name=%D0%92%D0%B8%D0%BA%D1%82%D0%BE%D1%80&surname=%D0%A6%D0%BE%D0%B9 .
в JavaScript есть функция encodeURIComponent для получения такой кодировки «вручную»:
Эта кодировка используется в основном для метода GET, то есть для передачи параметра в строке запроса. По стандарту строка запроса не может содержать произвольные Unicode-символы, поэтому они кодируются как показано выше.
GET-запрос
Формируя XMLHttpRequest, мы должны формировать запрос «руками», кодируя поля функцией encodeURIComponent .
Например, для посылки GET-запроса с параметрами name и surname , аналогично форме выше, их необходимо закодировать так:
Браузер автоматически добавит к запросу важнейшие HTTP-заголовки, такие как Content-Length и Connection .
По спецификации браузер запрещает их явную установку, как и некоторых других низкоуровневых HTTP-заголовков, которые могли бы ввести в заблуждение сервер относительно того, кто и сколько данных ему прислал, например Referer . Это сделано в целях контроля правильности запроса и для безопасности.
Запрос, отправленный кодом выше через XMLHttpRequest , никак не отличается от обычной отправки формы. Сервер не в состоянии их отличить.
Поэтому в некоторых фреймворках, чтобы сказать серверу, что это AJAX, добавляют специальный заголовок, например такой:
POST с urlencoded
В методе POST параметры передаются не в URL, а в теле запроса. Оно указывается в вызове send(body) .
В стандартных HTTP-формах для метода POST доступны три кодировки, задаваемые через атрибут enctype :
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
В зависимости от enctype браузер кодирует данные соответствующим способом перед отправкой на сервер.
В случае с XMLHttpRequest мы, вообще говоря, не обязаны использовать ни один из этих способов. Главное, чтобы сервер наш запрос понял. Но обычно проще всего выбрать какой-то из стандартных.
В частности, при POST обязателен заголовок Content-Type , содержащий кодировку. Это указание для сервера – как обрабатывать (раскодировать) пришедший запрос.
Для примера отправим запрос в кодировке application/x-www-form-urlencoded :
Всегда используется только кодировка UTF-8, независимо от языка и кодировки страницы.
Если сервер вдруг ожидает данные в другой кодировке, к примеру windows-1251, то их нужно будет перекодировать.
Кодировка multipart/form-data
Кодировка urlencoded за счёт замены символов на %код может сильно «раздуть» общий объём пересылаемых данных. Поэтому для пересылки файлов используется другая кодировка: multipart/form-data.
В этой кодировке поля пересылаются одно за другим, через строку-разделитель.
Чтобы использовать этот способ, нужно указать его в атрибуте enctype и метод должен быть POST:
Форма при такой кодировке будет выглядеть примерно так:
…То есть, поля передаются одно за другим, значения не кодируются, а чтобы было чётко понятно, какое значение где – поля разделены случайно сгенерированной строкой, которую называют «boundary» (англ. граница), в примере выше это RaNdOmDeLiMiTeR :
Сервер видит заголовок Content-Type: multipart/form-data , читает из него границу и раскодирует поля формы.
Такой способ используется в первую очередь при пересылке файлов, так перекодировка мегабайтов через urlencoded существенно загрузила бы браузер. Да и объём данных после неё сильно вырос бы.
Однако, никто не мешает использовать эту кодировку всегда для POST запросов. Для GET доступна только urlencoded.
POST с multipart/form-data
Сделать POST-запрос в кодировке multipart/form-data можно и через XMLHttpRequest.
Достаточно указать в заголовке Content-Type кодировку и границу, и далее сформировать тело запроса, удовлетворяющее требованиям кодировки.
Пример кода для того же запроса, что и раньше, теперь в кодировке multipart/form-data :
Тело запроса будет иметь вид, описанный выше, то есть поля через разделитель.
Можно создать запрос, который сервер воспримет как загрузку файла.
Для добавления файла нужно использовать тот же код, что выше, модифицировав заголовки перед полем, которое является файлом, так:
FormData
Современные браузеры, исключая IE9- (впрочем, есть полифил), поддерживают встроенный объект FormData, который кодирует формы для отправки на сервер.
Это очень удобно. Например:
Этот код отправит на сервер форму с полями name , surname и patronym .
- Конструктор new FormData([form]) вызывается либо без аргументов, либо с DOM-элементом формы.
- Метод formData.append(name, value) добавляет данные к форме.
Объект formData можно сразу отсылать, интеграция FormData с XMLHttpRequest встроена в браузер. Кодировка при этом будет multipart/form-data .
Другие кодировки
XMLHttpRequest сам по себе не ограничивает кодировку и формат пересылаемых данных.
Поэтому для обмена данными часто используется формат JSON:
Итого
- У форм есть две основные кодировки: application/x-www-form-urlencoded – по умолчанию и multipart/form-data – для POST запросов, если явно указана в enctype . Вторая кодировка обычно используется для больших данных и только для тела запроса.
- Для составления запроса в application/x-www-form-urlencoded используется функция encodeURIComponent .
- Для отправки запроса в multipart/form-data – объект FormData .
- Для обмена данными JS ↔ сервер можно использовать и просто JSON, желательно с указанием кодировки в заголовке Content-Type .
В XMLHttpRequest можно использовать и другие HTTP-методы, например PUT, DELETE, TRACE. К ним применимы все те же принципы, что описаны выше.
POST запрос, составное содержимое (multipart/form-data)

В жизни любого программиста попадаются задачки, которые человека цепляют. Вот не нравится стандартный метод решения и все! А порой бывает, что стандартные решения не подходят по какой-то причине. Некоторые люди обходят такие задачи стороной, другие же любят решать их. Можно даже сказать сами их находят. Одна из таких задач отсылка файла или несколько файлов методом POST.
Некоторые наверное скажут, эта задача совсем не задача. Ведь есть замечательная библиотека CURL, которая довольно простая и решает эту задачу легко! Но не спешите. Да, CURL мощная библиотека, да она загружает файлы, но… Как Вы знаете у нее есть маленькая особенность — файл должен быть размещен на жестком диске!
А теперь давайте представим себе такую ситуацию, Вы генерируете динамически файл или же он уже находится в памяти и нужно его отправить методом POST на удаленный Web сервер. Что же тогда получается? Перед его отправкой нужно его сохранить? Да именно так и поступило бы 90% программистов. Зачем искать лишние проблемы, если решение лежит на поверхности? Но мы же с Вами не из этих 90%! Мы же лучше, мы же можем решить любую задачку. Зачем нам лишнее действие? Во-первых, оно задействует не быструю файловую систему жесткого диска. Во-вторых, у нас может и не быть доступа к файловой системе или же там выделено слишком мало места.
Как же нам тогда решить эту задачку? Для этого надо взглянуть как собственно передаются данные методом POST. Единственный вариант решения — это передача файла составным запросом с помощью multipart/form-data. Этот метод хорошо описан в RFC7578. Давайте взглянем как будет выглядеть тело POST запроса multipart/form-data:
Наше тело состоит из двух частей, в первой части мы передаем значение поля формы name=«field» равное: text. Во второй части мы передаем поле name=«file» с содержимым файла filename=«sample.txt»: Content file. В заголовке мы указываем формат содержимого POST запроса — Content-Type: multipart/form-data, строку разделитель составных частей: boundary=————-573cf973d5228 и длину сообщения — Content-Length: 288.
Осталось, собственно, написать программу реализующий этот метод. Так как мы люди умные и не пишем по сто раз одно и тоже в разных проектах, то оформим все в виде класса реализующий этот метод. Плюс к этому, расширим его для разных вариантов отправки как файлов, так и простых элементов формы. А что бы отличить среди массива POST данных, наличие файла, создадим отдельный файл — контейнер с содержимым файла и его данных (имя и расширение). Таким образом он будет выглядеть следующим образом:
Теперь собственно сам класс по формированию тела multipart/form-data для POST запроса:
Данный класс состоит из нескольких методов. Метод — PartPost формирует отдельные части составного запроса, а метод — Get объединяет эти части и формирует тело POST запроса в формате — multipart/form-data.
Теперь у нас есть универсальный класс для отправки тела POST запроса. Осталось написать программу использующую данный класс для отправки файлов на удаленный Web сервер. Воспользуемся библиотекой CURL:
Если CURL не подходит, то данную библиотеку можно применить и для отправки через сокеты. Ну и собственно ссылки на источники:
How does HTTP file upload work?
When I submit a simple form like this with a file attached:
How does it send the file internally? Is the file sent as part of the HTTP body as data? In the headers of this request, I don’t see anything related to the name of the file.
I just would like the know the internal workings of the HTTP when sending a file.
5 Answers 5
Let’s take a look at what happens when you select a file and submit your form (I’ve truncated the headers for brevity):
NOTE: each boundary string must be prefixed with an extra — , just like in the end of the last boundary string. The example above already includes this, but it can be easy to miss. See comment by @Andreas below.
Instead of URL encoding the form parameters, the form parameters (including the file data) are sent as sections in a multipart document in the body of the request.
In the example above, you can see the input MAX_FILE_SIZE with the value set in the form, as well as a section containing the file data. The file name is part of the Content-Disposition header.
The full details are here.
How does it send the file internally?
- add some more HTML5 references
- explain why he is right with a form submit example
HTML5 references
There are three possibilities for enctype :
How to generate the examples
Once you see an example of each method, it becomes obvious how they work, and when you should use each one.
You can produce examples using:
- nc -l or an ECHO server: HTTP test server accepting GET/POST requests
- a user agent like a browser or cURL
Save the form to a minimal .html file:
We set the default text value to aωb , which means aωb because ω is U+03C9 , which are the bytes 61 CF 89 62 in UTF-8.
Create files to upload:
Run our little echo server:
Open the HTML on your browser, select the files and click on submit and check the terminal.
nc prints the request received.
Tested on: Ubuntu 14.04.3, nc BSD 1.105, Firefox 40.
multipart/form-data
For the binary file and text field, the bytes 61 CF 89 62 ( aωb in UTF-8) are sent literally. You could verify that with nc -l localhost 8000 | hd , which says that the bytes:
were sent ( 61 == ‘a’ and 62 == ‘b’).
Therefore it is clear that:
Content-Type: multipart/form-data; boundary=—————————735323031399963166993862150 sets the content type to multipart/form-data and says that the fields are separated by the given boundary string.
But note that the:
has two less dadhes — than the actual barrier
This is because the standard requires the boundary to start with two dashes — . The other dashes appear to be just how Firefox chose to implement the arbitrary boundary. RFC 7578 clearly mentions that those two leading dashes — are required:
4.1. "Boundary" Parameter of multipart/form-data
As with other multipart types, the parts are delimited with a boundary delimiter, constructed using CRLF, "—", and the value of the "boundary" parameter.
every field gets some sub headers before its data: Content-Disposition: form-data; , the field name , the filename , followed by the data.
The server reads the data until the next boundary string. The browser must choose a boundary that will not appear in any of the fields, so this is why the boundary may vary between requests.
Because we have the unique boundary, no encoding of the data is necessary: binary data is sent as is.
TODO: what is the optimal boundary size ( log(N) I bet), and name / running time of the algorithm that finds it? Asked at: https://cs.stackexchange.com/questions/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences
Content-Type is automatically determined by the browser.
application/x-www-form-urlencoded
Now change the enctype to application/x-www-form-urlencoded , reload the browser, and resubmit.
Clearly the file data was not sent, only the basenames. So this cannot be used for files.
As for the text field, we see that usual printable characters like a and b were sent in one byte, while non-printable ones like 0xCF and 0x89 took up 3 bytes each: %CF%89 !
Comparison
File uploads often contain lots of non-printable characters (e.g. images), while text forms almost never do.
From the examples we have seen that:
multipart/form-data : adds a few bytes of boundary overhead to the message, and must spend some time calculating it, but sends each byte in one byte.
application/x-www-form-urlencoded : has a single byte boundary per field ( & ), but adds a linear overhead factor of 3x for every non-printable character.
Therefore, even if we could send files with application/x-www-form-urlencoded , we wouldn’t want to, because it is so inefficient.
But for printable characters found in text fields, it does not matter and generates less overhead, so we just use it.