Request params error

I've read a lot of questions about 400 vs 422, but they are almost for HTTP POST requests for example this one: 400 vs 422 response to POST of data. I am still not sure what should I use for GET wh...

Let’s assume for a moment that the resource you are querying is returning a set of entries that do contain certain properties. If you don’t specify a filter you will basically get a (pageable) representation of those entries either as embedded objects or as links to those resources.

Now you want to filter the results based on some properties these entries have. Most programming languages nowadays provide some lambda functionality in the form of

List filteredList = list.filter(item => item.propertyX == ...)...;

The result of such a filter function is usually a list of items that fulfilled the specified conditions. If no items met the given condition then the result will be an empty list.

Applying certain filter conditions on the Web can be designed similarly. Is it really an error when a provided filter expression doesn’t yield any entries? IMO it is not an error in terms of the actual message transport itself as the server was able to receive and parse the request without any issues. As such it has to be some kind of business rule that states that only admissible values are allowed as input.

If you or your company consider the case of a provided filter value for a property returning no results as an error or you perform some i.e. XML or JSON schemata validation on the received payload (for complex requests) then we should look at how those mentioned HTTP errors are defined:

The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). (Source: RFC 7230)

Here, it is clearly the case that you don’t want to process requests that come with an invalid property value and as such decline the request as such.

The 422 (Unprocessable Entity) status code means the server understands the content type of the request entity (hence a 415(Unsupported Media Type) status code is inappropriate), and the syntax of the request entity is correct (thus a 400 (Bad Request) status code is inappropriate) but was unable to process the contained instructions. For example, this error condition may occur if an XML request body contains well-formed (i.e., syntactically correct), but semantically erroneous, XML instructions. (Source: RFC 4918 (WebDAV))

In this case you basically say that the payload was actually syntactically correct but failed on a semmantical level.

Note that 422 Unprocessable Entity stems from WebDAV while 400 Bad Request is defined in the HTTP specification. This can have some impact if your API serves arbitrary HTTP clients. Ones that only know and support the HTTP error codes defined in the HTTP sepcification won’t be able to really determine the semantics of the 422 response. They will still consider it as a user error, but won’t be able to provide the client with any more help on that issue. As such, if your API needs to be as generic as possible, stick to 400 Bad Request. If you are sure all clients support 422 Unprocessable Entity go for that one.

General improvement hints

As you tagged your question with rest, let’s see how we can improve this case.

REST is an architectural style with an intention of decoupling clients from servers to make the former one more failure tollerant while allowing the latter one to evolve freely over time. Servers in such architectures should therefore provide clients with all the things clients need to make valid requests. To avoid having clients to know upfront what a server expects as input, servers usually provide some kind of input mask clients can use to fill in stuff the server needs.

On the browsable Web this is usually accomplished by HTML Forms. The form not only teaches your client where to send the request to, which HTTP operation to use and which representation format the request should actually use (usually given implicitly as application/x-www-form-urlencoded) but also the sturcture and properties the server supports.

In HTML forms it is rather easy for the server to restrict the input choices of a client by using something along the lines of

<form action="/target">
  <label for="cars">Choose a car:</label>
  <select name="cars" id="cars">
    <option value="volvo">Volvo</option>
    <option value="saab">Saab</option>
    <option value="opel">Opel</option>
    <option value="audi">Audi</option>
  </select>
  <br/>
  <input type="submit" value="Submit">
</form>

This doesn’t really remove the needs to check and verify the correctness of the request on the server side, tough you make it much easier for the client to actually perform a valid request.

Unfortunately, HTML forms itself have their limits. I.e. they only allow POST and GET requests to be issues. While encType defaults to application/x-www-form-urlencoded, if you want to transfer files you should use multipart/form-data. Other than that, any valid content-type should be admissible.

If you prefer JSON-based payloads over HTML you might want to look into JSON Forms, HAL forms, Ion Forms among others.

Note though that you should adhere to the content type negotiation principles. Most often proactive content type negotiation is performed where a client sends its preferences within the Accept header and the server will select the best match somehow and return either the resource mapped to that representation format or respond with a 406 Not Acceptable response. While the standard doesn’t prevent returning a default representation in such caes, it bears the danger that clients won’t be able to process such responses then. A better alternative here would be to fall back to reactive negotiation where the server responds with a 300 Muliple Choice response where a client has to select one of the provided alternatives and then send a GET request to the selected alternative URIs to retrieve the content in the payload may be able to process.

If you want to provide a simple link a client can use to retrieve filtered results, the server should provide the client already with the full URI as well as a link relation name and/or extension relation type that the client can use to lookup the URI to retrieve the content for if interested in.

Both, forms and link-relation support, fall under the HATEOAS umbrella as they help to remove the need for any external documentation such as OpenAPI or Swagger documentation.


To sum things up, I would rethink whether a provided property value that does not exist should really end up as a business failure. I think returning an empty list is just fine here as you clearly state that way that for the given criterias no result was obtainable. If you though still want to stick to a business error check what clients actually make use of your API. If they support 422 go for that one. If you don’t know, better stick to 400 as it should be understood by all HTTP clients equally.

In oder to remove the likelihood of ending up with requests that issue invalid property values, use forms to teach clients how requests should look like. Through certain elements or properties you can already teach a client that only a limited set of choices is valid for a certain property. Instead of a form you could also provide dedicated links a client can just use to obtain the filtered result. Just make sure to issue those links with meaningful link relatin names then.

The spring boot error Required string parameter is not present occurs when the request parameter is not stated in the request url and the request parameter is configured as mandatory. The controller method needs to provide a value for the request parameter. The invoked url does not contain the value of the request parameter. If the value for the @RequestParam is not given, the exception Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter ‘name’ is not present] will be thrown.

The request param is configured using the annotation @RequestParam in spring boot. By default, the value of the @RequestParam annotation is mandatory.

In a rest call, request parameter can be made as mandatory by setting required=true. If a parameter is mandatory and the parameter value is not received by restcontroller, then this warning message is thrown in the application. Browser shows as There was an unexpected error (type=Bad Request, status=400).

Exception

2019-10-03 17:40:07.516  WARN 4304 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'name' is not present]

Error Message

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Thu Oct 03 17:42:47 IST 2019
There was an unexpected error (type=Bad Request, status=400).
Required String parameter 'name' is not present 

How to reproduce this issue

In Spring Boot Application, create a rest controller class. Create a method with a @Requestparam as a parameter in the rest controller class. Configure the request param as required=true. Now call this rest url without passing value to this request param.

package com.yawintutor.application;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@GetMapping("welcome")
	public String firstPage(@RequestParam(name = "name", required = true) String name) {
		return new String( "Welcome to "+name+"!");
	}
}

Output

$ curl -X GET http://localhost:8080/welcome?username=Yawin
{"timestamp":"2020-12-30T22:50:10.246+0000","status":400,"error":"Bad Request","message":"Required String parameter 'name' is not present","path":"/welcome"}

Root Cause

The request url does not contain the value of the request parameter. The request parameter is configured as the required parameter in the spring boot rest controller. When the request call to the controller method is reached, the error Required string parameter is not present is thrown as the required request parameter is not available.

Solution 1

The request parameter is added using the @Requestparam annotation. If the “required” attribute of the @RequestParam annotation is configured as true, the controller method expects the value for the request param. If the param value is an optional value, configure as required=false in the @RequestParam annotation.

In the example below, the required attribute is set to false so that the request parameter value is optional.

package com.yawintutor.application;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@GetMapping("welcome")
	public String firstPage(@RequestParam(name = "name", required = false) String name) {
		return new String( "Welcome to "+name+"!");
	}
}

Output

In this case, the value of the request param ‘name’ is null.

$ curl -X GET http://localhost:8080/welcome?username=Yawin
Welcome to null!

Solution 2

When the rest controller method requires a request param value and the request url can not send a request parameter, the @RequestParam annotation allows the default value to be configured. If the url has the value of the request param, the value will be used in the method. If the value of the request param is not passed in the url, the default value is used in the method.

package com.yawintutor.application;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@GetMapping("welcome")
	public String firstPage(@RequestParam(name = "name", required = false, defaultValue="unknown") String name) {
		return new String( "Welcome to "+name+"!");
	}
}

Output

$ curl -X GET http://localhost:8080/welcome?username=Yawin
Welcome to unknown!

Solution 3

The request param value is configured as required in the @RequestParam annotation of the rest controller method. The value of the request param must be sent to the url. The request param should be passed to the url as the key = value format. 

The example below shows how to configure the request param in the url request.

$ curl -X GET http://localhost:8080/welcome?name=Yawin
Welcome to Yawin!

Содержание

  1. Введение в тему
  2. Создание get и post запроса
  3. Передача параметров в url
  4. Содержимое ответа response
  5. Бинарное содержимое ответа
  6. Содержимое ответа в json
  7. Необработанное содержимое ответа
  8. Пользовательские заголовки
  9. Более сложные post запросы
  10. Post отправка multipart encoded файла
  11. Коды состояния ответа
  12. Заголовки ответов
  13. Cookies
  14. Редиректы и история
  15. Тайм ауты
  16. Ошибки и исключения

Введение в тему

Модуль python requests – это общепринятый стандарт для работы с запросами по протоколу HTTP.

Этот модуль избавляет Вас от необходимости работать с низкоуровневыми деталями. Работа с запросами становится простой и элегантной.

В этом уроке будут рассмотрены самые полезные функций библиотеки requests и различные способы их использования.

Перед использованием модуля его необходимо установить:

Создание get и post запроса

Сперва необходимо добавить модуль Requests в Ваш код:

Создадим запрос и получим ответ, содержащий страницу и все необходимые данные о ней.


import requests

response = requests.get('https://www.google.ru/')

В переменную response попадает ответ на запрос. Благодаря этому объекту можно использовать любую информацию, относящуюся к этому ответу.

Сделать POST запрос так же очень просто:


import requests

 

response = requests.post('https://www.google.ru/', data = {'foo':3})

Другие виды HTTP запросов, к примеру: PUT, DELETE, и прочих, выполнить ничуть не сложнее:


import requests

 

response = requests.put('https://www.google.ru/', data = {'foo':3})

response = requests.delete('https://www.google.ru/')

response = requests.head('https://www.google.ru/')

response = requests.options('https://www.google.ru/')

Передача параметров в url

Иногда может быть необходимо отправить различные данные вместе с запросом URL. При ручной настройке URL, параметры выглядят как пары ключ=значение после знака «?». Например, https://www.google.ru/search?q=Python. Модуль Requests предоставляет возможность передать эти параметры как словарь, применяя аргумент params. Если вы хотите передать q = Python и foo=’bar’ ресурсу google.ru/search, вы должны использовать следующий код:


import requests

params_dict = {'q':'Python', 'foo':'bar'}
response = requests.get('https://www.google.ru/search', params=params_dict)
print(response.url)

#Вывод:

https://www.google.ru/search?q=Python&foo=bar

Здесь мы видим, что URL был сформирован именно так, как это было задумано.

Пара ключ=значение, где значение равняется None, не будет добавлена к параметрам запроса URL.

Так же есть возможность передавать в запрос список параметров:


import requests

params_dict = {'q':'Python', 'foo':['bar', 'eggs']}
response = requests.get('https://www.google.ru/search', params=params_dict)
print(response.url)
#Вывод:

https://www.google.ru/search?q=Python&foo=bar&foo=eggs

Содержимое ответа response

Код из предыдущего листинга создаёт объект Response, содержащий ответ сервера на наш запрос. Обратившись к его атрибуту .url можно просмотреть адрес, куда был направлен запрос. Атрибут .text позволяет просмотреть содержимое ответа. Вот как это работает:


import requests

params_dict = {'q':'Python'}
response = requests.get('https://www.google.ru/search', params=params_dict)
print(response.text)
#Вывод:<!doctype html><html lang="ru"><head><meta charset="UTF-8"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png"…

Библиотека автоматически пытается определить кодировку ответа основываясь на его заголовках. Узнать, какую кодировку выбрал модуль, можно следующим образом:


import requests

params_dict = {'q':'Python'}
response = requests.get('https://www.google.ru/search', params=params_dict)
print(response.encoding)
#Вывод:

windows-1251

Можно так же самостоятельно установить кодировку используя атрибут .encoding.


import requests

params_dict = {'q':'Python'}
response = requests.get('https://www.google.ru/search', params=params_dict)
response.encoding = 'utf-8' # указываем необходимую кодировку вручную
print(response.encoding)
#Вывод:

utf-8

Бинарное содержимое ответа

Существует возможность просмотра ответа в виде байтов:


import requests

params_dict = {'q':'Python'}
response = requests.get('https://www.google.ru/search', params=params_dict)
print(response.content)
#Вывод:

b'<!doctype html><html lang="ru"><head><meta charset="UTF-8"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" …

При передаче со сжатием ответ автоматически декодируется для Вас.

Содержимое ответа в json

Так же в Requests есть встроенная обработка ответов в формате JSON:

import requests
import json

response = requests.get(‘http://api.open-notify.org/astros.json’)
print(json.dumps(response.json(), sort_keys=True, indent=4))
#Вывод:

{

«message»: «success»,

«number»: 10,

«people»: [

{

«craft»: «ISS»,

«name»: «Mark Vande Hei»

},

{

«craft»: «ISS»,

«name»: «Oleg Novitskiy»

},

[/dm_code_snippet]

Если ответ не является JSON, то .json выбросит исключение:


import requests
import json

response = requests.get('https://www.google.ru/search')
print(json.dumps(response.json(), sort_keys=True, indent=4))
#Вывод:

…

json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Необработанное содержимое ответа

Если Вам нужно получить доступ к ответу сервера в чистом виде на уровне сокета, обратитесь к атрибуту .raw. Для этого необходимо указать параметр stream=True в запросе. Этот параметр заставляет модуль читать данные по мере их прибытия.


import requests

response = requests.get('https://www.google.ru/', stream=True)
print(response.raw)
print('Q'*10)
print(response.raw.read(15))
#Вывод:

<urllib3.response.HTTPResponse object at 0x000001E368771FA0>

QQQQQQQQQQ

b'x1fx8bx08x00x00x00x00x00x02xffxc5[[sxdb'

Так же можно использовать метод .iter_content. Этот метод итерирует данные потокового ответа и это позволяет избежать чтения содержимого сразу в память для больших ответов. Параметр chunk_size – это количество байтов, которые он должен прочитать в памяти.  Параметр chunk_size можно произвольно менять.


import requests

response = requests.get('https://www.google.ru/', stream=True)
print(response.iter_content)
print('Q'*10)
print([i for i in response.iter_content(chunk_size=256)])
#Вывод:

<bound method Response.iter_content of <Response [200]>>

QQQQQQQQQQ

[b'<!doctype html><html itemscope="" itemtype="http://sche', b'ma.org/WebPage" lang="ru"><head><meta content=…

response.iter_content будет автоматически декодировать сжатый ответ. Response.raw — чистый набор байтов, неизменённое содержимое ответа.

Пользовательские заголовки

Если необходимо установить заголовки в HTTP запросе, передайте словарь с ними в параметр headers. Значения заголовка должны быть типа string, bytestring или unicode. Имена заголовков не чувствительны к регистру символов.
В следующем примере мы устанавливаем информацию об используемом браузере:


import requests

response = requests.get('https://www.google.ru/', headers={'user-agent': 'unknown_browser'})
print(response.request.headers)
# Вывод:

{'user-agent': 'unknown_browser', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}

Более сложные post запросы

Существует способ отправить данные так, будто это результат заполнения формы на сайте:


import requests

response = requests.post('https://httpbin.org/post', data={'foo': 'bar'})
print(response.text)
# Вывод:

{

"args": {},

"data": "",

"files": {},

"form": {

"foo": "bar"

},

"headers": {

…

Параметр data может иметь произвольное количество значений для каждого ключа. Для этого необходимо указать data в формате кортежа, либо в виде dict со списками значений.


import requests

response = requests.post('https://httpbin.org/post', data={'foo':['bar', 'eggs']})
print(response.json()['form'])
print('|'*10)
response = requests.post('https://httpbin.org/post', data=[('foo', 'bar'), ('foo', 'eggs')])
print(response.json()['form'])
# Вывод:

{'foo': ['bar', 'eggs']}

||||||||||

{'foo': ['bar', 'eggs']}

Если нужно отправить данные, не закодированные как данные формы, то передайте в запрос строку вместо словаря. Тогда данные отправятся в изначальном виде.


import requests

response = requests.post('https://httpbin.org/post', data={'foo': 'bar'})
print('URL:', response.request.url)
print('Body:', response.request.body)
print('-' * 10)
response = requests.post('https://httpbin.org/post', data='foo=bar')
print('URL:', response.request.url)
print('Body:', response.request.body)
# Вывод:

URL: https://httpbin.org/post

URL: https://httpbin.org/post

Body: foo=bar

----------

URL: https://httpbin.org/post

Body: foo=bar

Post отправка multipart encoded файла

Запросы упрощают загрузку файлов с многостраничным кодированием (Multipart-Encoded):


import requests

url = 'https://httpbin.org/post'

files = {'file': open('report.xls', 'rb')}

response = requests.post(url, files=files)

print(response.text)

# Вывод:

{

...

"files": {

"file": "<censored...binary...data>"

},

...

}

Вы можете установить имя файла, content_type и заголовки в явном виде:


import requests

url = 'https://httpbin.org/post'

files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}

response = requests.post(url, files=files)

print(response.text)

# Вывод:

{

...

"files": {

"file": "<censored...binary...data>"

},

...

}

Можете отправить строки, которые будут приняты в виде файлов:


import requests

url = 'https://httpbin.org/post'

files = {'file': ('report.csv', 'some,data,to,sendnanother,row,to,sendn')}

response = requests.post(url, files=files)

print(response.text)

# Вывод:

{

...

"files": {

"file": "some,data,to,send\nanother,row,to,send\n"

},

...

}

Коды состояния ответа

Возможно, наиболее важные данные (первые – уж точно), которые вы можете получить, используя библиотеку requests, является код состояния ответа.

Так, 200 статус означает, что запрос выполнен успешно, тогда как 404 статус означает, что ресурс не найден.

Важнее всего то, с какой цифры начинается код состояния:

  • 1XX — информация
  • 2XX — успешно
  • 3XX — перенаправление
  • 4XX — ошибка клиента (ошибка на нашей стороне)
  • 5XX — ошибка сервера (самые страшные коды для разработчика)

Используя атрибут .status_code можно получить статус, который вернул сервер:


import requests

response = requests.get('https://www.google.ru/')
print(response.status_code)

# Вывод:

200

.status_code вернул 200 — это означает, что запрос успешно выполнен и сервер вернул запрашиваемые данные.

При желании, такую информацию можно применить в Вашем Пайтон скрипте для принятия решений:


import requests

response = requests.get('https://www.google.ru/')
if response.status_code == 200:    print('Успех!')elif response.status_code == 404:    print('Страница куда-то пропала…')

# Вывод:

Успех!

Если код состояния response равен 200, то скрипт выведет «Успех!», но, если он равен 404, то скрипт вернёт «Страница куда-то пропала…».

Если применить модуль Response в условном выражении и проверить логическое значение его экземпляра (if response) то он продемонстрирует значение True, если код ответа находится в диапазоне между 200 и 400, и False во всех остальных случаях.

Упростим код из предыдущего примера:


import requests

response = requests.get('https://www.google.ru/fake/')
if response:
print('Успех!')
else:
print('Хьюстон, у нас проблемы!')
# Вывод:

Хьюстон, у нас проблемы!

Данный способ не проверяет, что код состояния равен именно 200.
Причиной этого является то, что response с кодом в диапазоне от 200 до 400, такие как 204 и 304, тоже являются успешными, ведь они возвращают обрабатываемый ответ. Следовательно, этот подход делит все запросы на успешные и неуспешные – не более того. Во многих случаях Вам потребуется более детальная обработка кодов состояния запроса.

Вы можете вызвать exception, если requests.get был неудачным. Такую конструкцию можно создать вызвав .raise_for_status() используя конструкцию try- except:


import requests

from requests.exceptions import HTTPError

for url in ['https://www.google.ru/', 'https://www.google.ru/invalid']:
try:
response = requests.get(url)

response.raise_for_status()
except HTTPError:
print(f'Возникла ошибка HTTP: {HTTPError}')
except Exception as err:
print(f'Возникла непредвиденная ошибка: {err}')
else:
print('Успех!')
# Вывод:

Успех!

Возникла ошибка HTTP: <class 'requests.exceptions.HTTPError'>

Заголовки ответов

Мы можем просматривать заголовки ответа сервера:


import requests

response = requests.get('https://www.google.ru/')
print(response.headers)
# Вывод:

{'Date': 'Sun, 27 Jun 2021 13:43:17 GMT', 'Expires': '-1', 'Cache-Control': 'private, max-age=0', 'Content-Type': 'text/html; charset=windows-1251', 'P3P': 'CP="This is not a P3P policy! See g.co/p3phelp for more info."', 'Content-Encoding': 'gzip', 'Server': 'gws', 'X-XSS-Protection': '0', 'X-Frame-Options': …

Cookies

Можно просмотреть файлы cookie, которые сервер отправляет вам обратно с помощью атрибута .cookies. Запросы также позволяют отправлять свои собственные cookie-файлы.

Чтобы добавить куки в запрос, Вы должны использовать dict, переданный в параметр cookie.


import requests

url = 'https://www.google.ru/'
headers = {'user-agent': 'your-own-user-agent/0.0.1'}
cookies = {'visit-month': 'February'}

response = requests.get(url, headers=headers, cookies=cookies)

print(response.request.headers)
# Вывод:

{'user-agent': 'your-own-user-agent/0.0.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Cookie': 'visit-month=February'}

Редиректы и история

По умолчанию модуль Requests выполняет редиректы для всех HTTP глаголов, кроме HEAD.

Существует возможность использовать параметр history объекта Response, чтобы отслеживать редиректы.

Например, GitHub перенаправляет все запросы HTTP на HTTPS:


import requests

response = requests.get('https://www.google.ru/')
print(response.url)
print(response.status_code)
print(response.history)
# Вывод:

https://www.google.ru/

200

[]

Тайм ауты

Так же легко можно управлять тем, сколько программа будет ждать возврат response. Время ожидания задаётся параметром timeout. Это очень важный параметр, так как, если его не использовать, написанный Вами скрипт может «зависнуть» в вечном ожидании ответа от сервера. Используем предыдущий код:


import requests

response = requests.get(‘https://www.google.ru/’, timeout=0.001)
print(response.url)
print(response.status_code)
print(response.history)
# Вывод:

raise ConnectTimeout(e, request=request)

requests.exceptions.ConnectTimeout: HTTPSConnectionPool(host=’www.google.ru’, port=443): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x000001E331681C70>, ‘Connection to www.google.ru timed out. (connect timeout=0.001)’))

Модуль не ждёт полной загрузки ответа. Исключение возникает, если сервер не отвечает (хотя бы один байт) за указанное время.

Ошибки и исключения

Если возникнет непредвиденная ситуация – ошибка соединения, модуль Requests выбросит эксепшн ConnectionError.

response.raise_for_status() возвращает объект HTTPError, если в процессе произошла ошибка. Его применяют для отладки модуля и, поэтому, он является неотъемлемой частью запросов Python.

Если выйдет время запроса, вызывается исключение Timeout. Если слишком много перенаправлений, то появится исключение TooManyRedirects.


Validate your request parameters using middleware in node.js

JavaScript being a weakly typed language makes it is really hard for developers to validate any type of variables.

Lack of consistency and a well-defined framework doesn’t help either.

1. A middleware that can validate the request parameters.

2. What is a middleware?

3. Required Parameter check

4. Type check

5. Other validation checks

6. Checking the performance of Simple validator

7. Writing tests for simple request parameter validation middleware

8. Using Joi as an alternative to adding request parameter validator

9. Checking the performance of JOI validator

10. Using ajv as an alternative to adding request parameter validator

11. Checking the performance of AJV validator

12. Edit 1: Using Express-Validation for the validation

A few months ago while working on big Node.js application, handling millions of requests per hour, I noticed it too.

Every API was literally checking if a certain parameter was being passed by the frontend correctly.

The same validations were repeated all over the codebase again and again.

Every time I had to create a new route/new API endpoint I had to write the same validation code.

For a OOP based programmer turned JS developer this was a bit of a shock.

For example:

// Inside the route. 
if (!request.phone_number) {
    throw new Error('Main request parameter not present.');
}

In this post we are going to learn the simplest way in which you can validate your request parameter or any other thing for that matter.

If you are starting out your app, this might look okay to you but as your app keeps getting bigger and bigger, you might want to write some specific validation code that can handle this for you out of the box.

If you’re just getting started for your application, I would suggest you skip this article( But do subscribe so that you can receive other good stuff that I write about) and come back to it when you have some good number of developers editing your code.

This article is for someone who wants to wrap all his requests with some special validations.

This will also be helpful for your backend developers to know which parameters are required for the given route and which of them are optional. All you need to do is create a middleware which will keep account of all your parameters.

A good example for which middleware can be used is logging. You can log your request parameters, headers, response data to whatever logging system you are using.

We are only going to talk about a specific type of middleware in this article, but you can extend it to anything you want to.

A middleware that can validate the request parameters.

How would you feel when you don’t have to worry about the incoming request parameters in the routes?

Pretty awesome, right!

You will never have to make any check related to the request parameters. All these checks will be transported to this single middleware.

What is a middleware?

Middleware is a piece of code that is used to make changes to request or response data.

Let’s start by writing a simple request validation middleware.

So this is the simple structure of middleware in Node.js. There are three types of checks that are going on in there.

Required Parameter check

This check tries to find if the parameter being requested is required or not. We can specify this while specifying the schema of the route parameters. I will share this schema a little later in this tutorial. If the parameter is required for the route and is not present in the parameters of the route, it will simply raise 400.

This can also give a custom message specifying which param exactly is not present in the request parameters. This part is described on line 21.

Type check

JavaScript being less strict related to the type of the variables, we want to add a check which will try to check if the type specified in the schema of the route parameters is the same as the type received from the request parameters.

This part of the code is written on line 6.

Other validation checks

There are multiple occasions when you want to add your own validations to request parameters. For example, you don’t want the value to be equal to 0. You can simply create the function and pass it in the schema of the route parameters. These checks are written on line 13.

Here is the schema for route parameters.

This is how you will create a route in your NodeJS app.

The cool thing about this is you can at any time integrate your own checks into this and you don’t have to worry about the error messages passed to frontend. They are handled as well.

Simple Validator Required Parameter check

Following error is produced when the required parameter is not present in the request body.

Simple Validator Parameter check

Simple Validator Length check

The following error is produced when the length of the required parameter is not accurate.

Simple Validator length check

Simple Validator Wrong type check

The following error is produced when the type of the required parameter is not accurate.

Simple Validator Wrong type check

Checking the performance of Simple validator

The above validation is trying to apply three checks.

  1. Given param, abc must be a present.
  2. Given param, abc must be a String.
  3. Given param, abc must be of length 10.

I will use all three of these scenarios and try to find out the time taken to raise the error and give back the response.

How am I checking the performance?

Javascript provides a very clean way to find out the time taken from one point to the next point. All you have to do is write console.time('') at the place where you want to start and console.timeEnd('') where you want to stop.

Passed string will be used as a reference, for example, I am using console.time('start') and console.timeEnd('start'). i.e. the string passed should be the same, in both the cases.

I added them to the middleware code.

const validateParams = function (requestParams) {
    return function (req, res, next) {
        console.time('start');
            ...
                if (!checkParamType(reqParam, param)) {
                    console.timeEnd('start');
                } else {
                    if (!runValidators(reqParam, param)) {
                        console.timeEnd('start');
                        ...
            } else if (param.required){
                console.timeEnd('start');
                ...
    }
};

Finally, I ran the APIs for each error type separately for 5 times and took their average. These were the final scores.

No parameter present in the body: 0.3228ms
Wrong length of the parameter: 0.7242ms
Wrong type of the parameter: 1.131ms( Used integer)

Writing tests for simple request parameter validation middleware

One of my colleagues asked me to write tests for this framework as this was going to be used at a lot of places and I agreed with him. But I was a little skeptical on how can we test this framework. After some Googling and StackOverflowing, I was able to test this framework. Here is the code for this.

Using Joi as an alternative to adding request parameter validator

I later found that you can use Joi for adding validations to parameters. This is a good option and you can use it if you want.

They provide a lot of validations out of the box which are easy to plug in.

Of course, you will have to install Joi to use it.

Your 50 line of middleware code will just reduce to 26 lines.

const Joi = require('joi');
const lodash = require('lodash');

const validateParams = function (paramSchema) {
    return async (req, res, next) => {
        const schema = Joi.object().keys(paramSchema);
        const paramSchemaKeys = Object.keys(paramSchema);
        let requestParamObj = {};
        for (let key of paramSchemaKeys){
            requestParamObj[key] = lodash.get(req.body, key);
        }
        try{
            await Joi.validate(requestParamObj, schema);
        } catch (err) {
            return res.send(400, {
                status: 400,
                result: err.details[0].message
            });
        }
        next();
    }
};

module.exports = {
    validateParams: validateParams
};

The main thing to look at here is the

await Joi.validate(requestParamObj, schema);

Similarly, you will have to set up your route as follows.

router.post('/abc', validateParams({
    abc: Joi.string().length(10).required(),
}), routeFunction);

Pretty clean right, I like this method more than creating something new of my own. But you will have to keep in mind that this will increase your bundle size. This is something that you have to think on your own and make a decision.

Joi Required Parameter check

The following error is produced when the required parameter is not present in the request body.

Joi Required Parameter check

Joi Length check

The following error is produced when the length of the required parameter is not accurate.

Joi length check

Joi Wrong type check

The following error is produced when the type of the required parameter is not accurate.

Joi Wrong type check

Validator Success response

Successful response.

Validator Success response

Checking the performance of JOI validator

I am using the same setup as I was using while checking the performance of Simple validator. Following are the results.

No parameter present in the body: 1.0778ms
Wrong length of the parameter: 5.6514ms
Wrong type of the parameter: 14.3162ms( Used integer)

I will use the same formula in the other libraries as well.

Using ajv as an alternative to adding request parameter validator

Then, we tried ajv for validating our requests.

Validate your request param using ajv in node.js

Why ajv?

ajv uses the JSON schema validation which is kind of a standard in any language, and we can keep using it, even if we move to another language in the future.

Also, they consider themselves as the fastest JSON schema validator out there and speed is something that we care for a lot.

Install ajv using the following command.

Finally, write the ajv validation middleware as follows.

const Ajv = require('ajv');
const lodash = require('lodash');

const validateParams = function (paramSchema) {
    return async (req, res, next) => {
        const ajv = new Ajv({$data: true});
        const paramSchemaKeys = Object.keys(paramSchema.properties);
        let requestParamObj = {};
        for (let key of paramSchemaKeys){
            // Use req.query/req.params if you want to validate query params.
            requestParamObj[key] = lodash.get(req.body, key);
        }
        const validated = ajv.validate(paramSchema, requestParamObj);
        if (!validated) {
            return res.send(400, {
                status: 400,
                result: getCustomMessage(ajv.errors[0])
            })
        }
        next();
    }
};

const getCustomMessage = (errorObject) => {
    if (['minLength', 'maxLength'].includes(errorObject.keyword)) {
        return `${errorObject.dataPath.replace('.', '')} ${errorObject.message}`;
    }
    return errorObject.message;
};

module.exports = {
    validateParams: validateParams
};

All you have to worry about is the ajv.validate function, which is used to carry out the validations. Unlike Joi, this validate method is not async.

The validation definition method is a little different for ajv.

router.post('/abc', validateParams({
		properties: {
			abc: {
				type: 'string',
				maxLength: 10,
				minLength: 10
			},
		},
		required: ['abc']
	}), routeFunction);

AJV Required Parameter check

Following error is produced when required parameter is not present in the request body.

AJV Required Parameter check

AJV length check

Following error is produced when the length of the required parameter is not accurate.

AJV length check

AJV Wrong type check

Following error is produced when the type of the required parameter is not accurate.

AJV Wrong type check

Checking the performance of AJV validator

I am using the same setup as I was using while checking the performance of Simple validator.

No parameter present in the body: 97.44ms
Wrong length of the parameter: 62.88ms
Wrong type of the parameter: 82.227ms( Used integer)

According to the above numbers, it is pretty clear that middleware using a Simple validator does perform better by big margins. You can choose whichever you want to use.

Edit 1: Using Express-Validation for the validation

Express-Validation is a package build on the top of Joi and can be used to validate your request parameters fairly easily. You don’t have to worry about this writing a separate middleware.

Install the package using the following command.

npm i express-validation --save

Adding the middleware is as simple as the following code.

const express = require('express')
const bodyParser = require('body-parser')
const { validate, ValidationError, Joi } = require('express-validation')
 
const loginValidation = {
  body: Joi.object({
    email: Joi.string()
      .email()
      .required(),
    password: Joi.string()
      .regex(/[a-zA-Z0-9]{3,30}/)
      .required(),
  }),
}
 
const app = express();
app.use(bodyParser.json())
 
app.post('/login', validate(loginValidation, {}, {}), (req, res) => {
  res.json(200)
})
 
app.use(function(err, req, res, next) {
  if (err instanceof ValidationError) {
    return res.status(err.statusCode).json(err)
  }
 
  return res.status(500).json(err)
})
 
app.listen(3000)

Express-Validation middleware

I hope you guys will like the idea behind the post. Please share it with your colleagues and let me know on social media platforms.

I am also open to other standards that are followed in the market. Please leave your ideas in the comments.

This guide covers mapping the request query string parameters to Spring Controller method arguments using @RequestParam annotation.

Overview

Spring Web provides the @RequestParam annotation for extracting and mapping query string parameters of a request into Spring Controller’s method arguments. Without that, we’ll have to pull the parameter values manually from the HttpServletRequest and cast them to variables of the desired types.

Spring, along with the annotation, saves us from manual work. It also provides flexible ways of reading single-value, multi-value, or optional parameters and makes them available as method arguments.

Before we see the @RequestParam in action, we will look at the annotation itself.

Spring’s @RequestParam annotation maps request parameters to the arguments of a request handler method in Spring Controllers. We can use this annotation on method parameters to bind request parameters by their names.

Interestingly, this annotation is available in Spring Web and Spring WebFlux annotation-based controllers.

@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; String defaultValue() default "nttnttnue000ue001ue002nttttn"; }

Code language: Java (java)

There are only three attributes in this annotation. The name attribute denotes the name of the request param, and we can omit it if the method and request parameters have the same name. The required attribute represents if the request parameter is mandatory, which is true by default. Lastly, the default attribute can specify a fall-back value of a request parameter, which will be used only when the request parameter isn’t present.

Using @RequestParam

To see the @RequestParam in action, we’ll write a @GetMapping method in a Spring @RestController and execute the endpoint with requests contenting query parameters.

Reading Single Query String Parameter

We want to read a single query string parameter from a request and map it to the controller method argument.

@GetMapping("/data1") public String singleParam(@RequestParam String id) { return "id: " + id; }

Code language: Java (java)

Behind the scenes, Spring looks for a query string parameter ‘id‘ and extracts its value from the request. Then it invokes the request handler singleParam(Id) and passes the param value as an argument.

To test this controller, let’s execute the GET /data endpoint with the id request parameter.

~ curl 'http://localhost:8080/data1?id=112' -- id: 112

Code language: Bash (bash)

Reading Multiple Query String Parameters

Similarly, if the request has more than one query string parameter, we can use @RequestParam annotation individually on the respective method arguments.

@GetMapping("/data2") public String multiParam( @RequestParam String id, @RequestParam String name) { return "id: " + id + ", name: " + name; }

Code language: Java (java)

Our controller reads the id and name parameters from the request.

~ curl 'http://localhost:8080/data2?id=112&name=Jon' -- id: 112, name: Jon

Code language: Bash (bash)

Executing the respective GET request, we see that both request parameters are mapped correctly.

Mapping Query String Parameters with Specific Type

By default, all the request query string parameters are represented as String. However, our method argument can describe the variable to a specific data type. Interestingly, Spring takes care of this type-casting internally.

For example, our Spring Controller reads a request parameter as a Long value.

@GetMapping("/data3") public String typedParam(@RequestParam Long id) { return "id: " + id; }

Code language: Java (java)

If Spring cannot cast the param value, it returns HTTP Status 400 (BAD_REQUEST), as shown.

~ curl -i 'http://localhost:8080/data3?id=abc' -- HTTP/1.1 400 ...

Code language: Bash (bash)

Reading MultiValue Query String Parameters

Any query string parameter in a request can have multiple values. That means they can appear multiple times in the URL or have comma-separated values. Spring @RequestParam annotation maps such request parameters to a collection- for example, List.

@GetMapping("/data4") public String multiValueParams(@RequestParam List<String> id) { return "id: " + id; }

Code language: Java (java)

Alternatively, we can also use an Array type or a Set type. Using the parameter of Set type ensures that the query string has unique values.

Let’s execute a request with a comma-separated multi-value parameter.

~ curl 'http://localhost:8080/data4?id=12,13,15,16' -- id: [12, 13, 15, 16]

Code language: Bash (bash)

Or a request having the same parameter multiple times with different values.

~ curl 'http://localhost:8080/data4?id=12&id=13&id=15' -- id: [12, 13, 15]

Code language: Bash (bash)

Making Query String Parameters Optional

By default, all the request parameters annotated with the @RequestParam are mandatory. Thus in the previous examples, if we skip a query string parameter from a request, we get a Bad Request response.

~ curl -i 'http://localhost:8080/data4' -- HTTP/1.1 400 ...

Code language: Bash (bash)

Now, let’s see the three ways of using @RequestParam annotation on optional request parameters.

Using @RequestParam required=false

To support optional query string parameters in a Spring controller, we can use the @RequestParam with the required=false flag.

@GetMapping("/data5") public String optionalParams (@RequestParam(required = false) Long id) { return "id: " + id; }

Code language: Java (java)

Now, we can omit the id parameter from our request.

~ curl 'http://localhost:8080/data5' -- id: null

Code language: JavaScript (javascript)

Using @RequestParam with Java Optional

Alternatively, we can use the Java Optional to make a particular query string parameter in a request optional. We can also provide a default value for a missing query string using Java Optional.

@GetMapping("/data6") public String javaOptionalParams (@RequestParam Optional<String> id) { return "id: " + id.orElseGet(() -> "Unknown"); }

Code language: Java (java)

The example shows how to use Java Optional to make a Spring Controller request parameter not mandatory.

~ curl 'http://localhost:8080/data6' -- id: Unknown

Code language: Bash (bash)

Using @RequestParam defaultValue

Lastly, we can use the defaultValue attribute of the @RequestParam annotation to specify a default value of a request parameter. Spring uses the provided defaultValue only when the actual parameter in the request is absent or empty.

@GetMapping("/data7") public String defaultParams (@RequestParam(defaultValue = "Unknown") String id) { return "id: " + id; }

Code language: Java (java)

Let’s test this by executing a GET request without any parameters.

~ curl 'http://localhost:8080/data7' -- id: Unknown

Code language: Bash (bash)

Mapping Query String Parameters by Name

In the previous examples, the controller method argument and a request’s respective query string parameter had the same name. Thankfully, we can use the name attribute of Spring @RequestParam when the query string parameter name is different than that of the method argument.

@GetMapping("/data8") public String namedParams (@RequestParam(name = "id") String dataId) { return "dataId: " + dataId; }

Code language: Java (java)

Reading Query String Parameters as Java Map

With the Spring @RequestParam annotation, we can collect all the request query string parameters as a HashMap. That is useful when we want to read all the query string parameters and their values together.

@GetMapping("/data9") public String mappedParams (@RequestParam Map<String, String> dataQuery) { return dataQuery.toString(); }

Code language: Java (java)

~ curl 'http://localhost:8080/data9?id=12&year=2034' -- {id=12, year=2034}

Code language: Bash (bash)

Summary

We have covered using Spring @RequestParam annotation to map query string parameters to controller method arguments. This annotation is available for both Spring MVC and Spring WebFlux.

In this tutorial, we had a detailed overview of the @RequestParam annotation and covered various scenarios of mapping request parameters to controller method arguments.

Please refer to our GitHub Repository for the complete source code of the examples used here.

Related Posts:

  • Spring @PathVariable Examples
  • Return Specific HTTP Response Status in Spring
  • Read HTTP Headers in Spring REST Controller
  • Custom Error Messages in Spring REST API
  • Custom Media Types in Spring REST API

Прежде чем начать, убедитесь, что установлена последняя версия Requests.

Для начала, давайте рассмотрим простые примеры.

Импортируйте модуль Requests:

Попробуем получить веб-страницу с помощью get-запроса. В этом примере давайте рассмотрим общий тайм-лайн GitHub:

r = requests.get('https://api.github.com/events')

Мы получили объект Response с именем r. С помощью этого объекта можно получить всю необходимую информацию.

Простой API Requests означает, что все типы HTTP запросов очевидны. Ниже приведен пример того, как вы можете сделать POST запрос:

r = requests.post('https://httpbin.org/post', data = {'key':'value'})  

Другие типы HTTP запросов, такие как : PUT, DELETE, HEAD и OPTIONS так же очень легко выполнить:

r = requests.put('https://httpbin.org/put', data = {'key':'value'})  
r = requests.delete('https://httpbin.org/delete')  
r = requests.head('https://httpbin.org/get')  
r = requests.options('https://httpbin.org/get')  

Передача параметров в URL

Часто вам может понадобится отправить какие-то данные в строке запроса URL. Если вы настраиваете URL вручную, эти данные будут представлены в нем в виде пар ключ/значение после знака вопроса. Например, httpbin.org/get?key=val. Requests позволяет передать эти аргументы в качестве словаря, используя аргумент params. Если вы хотите передать key1=value1 и key2=value2 ресурсу httpbin.org/get, вы должны использовать следующий код:

payload = {'key1': 'value1', 'key2': 'value2'}  
r = requests.get('https://httpbin.org/get', params=payload)
print(r.url) 

Как видно, URL был сформирован правильно:

https://httpbin.org/get?key2=value2&key1=value1

Ключ словаря, значение которого None, не будет добавлен в строке запроса URL.

Вы можете передать список параметров в качестве значения:

>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}  
>>> r = requests.get('https://httpbin.org/get', params=payload)  
>>> print(r.url)  
https://httpbin.org/get?key1=value1&key2=value2&key2=value3 

Содержимое ответа (response)

Мы можем прочитать содержимое ответа сервера. Рассмотрим снова тайм-лайн GitHub:

>>> import requests
>>> r = requests.get('https://api.github.com/events')
>>> r.text
'[{"repository":{"open_issues":0,"url":"https://github.com/...

Requests будет автоматически декодировать содержимое ответа сервера. Большинство кодировок unicode декодируются без проблем.
Когда вы делаете запрос, Requests делает предположение о кодировке, основанное на заголовках HTTP. Эта же кодировка текста, используется при обращение к r.text. Можно узнать, какую кодировку использует Requests, и изменить её с помощью r.encoding:

>>> r.encoding
'utf-8'
>>> r.encoding = 'ISO-8859-1'

Если вы измените кодировку, Requests будет использовать новое значение r.encoding всякий раз, когда вы будете использовать r.text. Вы можете сделать это в любой ситуации, где нужна более специализированная логика работы с кодировкой содержимого ответа.

Например, в HTML и XML есть возможность задавать кодировку прямо в теле документа. В подобных ситуациях вы должны использовать r.content, чтобы найти кодировку, а затем установить r.encoding. Это позволит вам использовать r.text с правильной кодировкой.

Requests может также использовать пользовательские кодировки в случае, если в них есть потребность. Если вы создали свою собственную кодировку и зарегистрировали ее в модуле codecs, используйте имя кодека в качестве значения r.encoding.

Бинарное содержимое ответа

Вы можете также получить доступ к телу ответа в виде байтов для не текстовых ответов:

>>> r.content
b'[{"repository":{"open_issues":0,"url":"https://github.com/...

Передача со сжатием gzip и deflate автоматически декодируются для вас.

Например, чтобы создать изображение на основе бинарных данных, возвращаемых при ответе на запрос, используйте следующий код:

from PIL import Image  
from io import BytesIO  

i = Image.open(BytesIO(r.content))

Содержимое ответа в JSON

Если вы работаете с данными в формате JSON, воспользуйтесь встроенным JSON декодером:

>>> import requests
>>> r = requests.get('https://api.github.com/events')
>>> r.json()
[{'repository': {'open_issues': 0, 'url': 'https://github.com/...

Если декодирование в JSON не удалось, r.json() вернет исключение. Например, если ответ с кодом 204 (No Content), или на случай если ответ содержит не валидный JSON, попытка обращения к r.json() будет возвращать ValueError: No JSON object could be decoded.

Следует отметить, что успешный вызов r.json() не указывает на успешный ответ сервера. Некоторые серверы могут возвращать объект JSON при неудачном ответе (например, сведения об ошибке HTTP 500). Такой JSON будет декодирован и возвращен. Для того, чтобы проверить успешен ли запрос, используйте r.raise_for_status() или проверьте какой r.status_code.

Необработанное содержимое ответа

В тех редких случаях, когда вы хотите получить доступ к “сырому” ответу сервера на уровне сокета, обратитесь к r.raw. Если вы хотите сделать это, убедитесь, что вы указали stream=True в вашем первом запросе. После этого вы уже можете проделать следующее:

>>> r = requests.get('https://api.github.com/events', stream=True)
>>> r.raw

>>> r.raw.read(10)
'x1fx8bx08x00x00x00x00x00x00x03'

Однако, можно использовать подобный код как шаблон, чтобы сохранить результат в файл:

with open(filename, 'wb') as fd:
    for chunk in r.iter_content(chunk_size=128):
        fd.write(chunk)

Использование r.iter_content обработает многое из того, с чем бы вам пришлось иметь дело при использовании r.raw напрямую. Для извлечения содержимого при потоковой загрузке, используйте способ, описанный выше. Обратите внимание, что chunk_size можно свободно скорректировать до числа, которое лучше подходит в вашем случае.

Важное замечание об использовании Response.iter_content и Response.raw. Response.iter_content будет автоматически декодировать gzip и deflate. Response.raw — необработанный поток байтов, он не меняет содержимое ответа. Если вам действительно нужен доступ к байтам по мере их возврата, используйте Response.raw.

Пользовательские заголовки

Если вы хотите добавить HTTP заголовки в запрос, просто передайте соответствующий dict в параметре headers.
Например, мы не указали наш user-agent в предыдущем примере:

url = 'https://api.github.com/some/endpoint'  
headers = {'user-agent': 'my-app/0.0.1'}  
r = requests.get(url, headers=headers)

Заголовкам дается меньший приоритет, чем более конкретным источникам информации. Например:

  • Заголовки авторизации, установленные с помощью headers= будут переопределены, если учетные данные указаны .netrc, которые, в свою очередь переопределены параметром auth=.
  • Они же будут удалены при редиректе.
  • Заголовки авторизации с прокси будут переопределены учетными данными прокси-сервера, которые указаны в вашем URL.
  • Content-Length будут переопределены, когда вы определите длину содержимого.

Кроме того, запросы не меняют свое поведение вообще, основываясь на указанных пользовательских заголовках.

Значения заголовка должны быть string, bytestring или unicode. Хотя это разрешено, рекомендуется избегать передачи значений заголовков unicode.

Более сложные POST запросы

Часто вы хотите послать некоторые form-encoded данные таким же образом, как это делается в HTML форме. Для этого просто передайте соответствующий словарь в аргументе data. Ваш словарь данных в таком случае будет автоматически закодирован как HTML форма, когда будет сделан запрос:

>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.post("https://httpbin.org/post", data=payload)
>>> print(r.text)
{
  ...
  "form": {
    "key2": "value2",
    "key1": "value1"
  },
  ...
}

Аргумент data также может иметь несколько значений для каждого ключа. Это можно сделать, указав data в формате tuple, либо в виде словаря со списками в качестве значений. Особенно полезно, когда форма имеет несколько элементов, которые используют один и тот же ключ:

>>> payload_tuples = [('key1', 'value1'), ('key1', 'value2')]
>>> r1 = requests.post('https://httpbin.org/post', data=payload_tuples)
>>> payload_dict = {'key1': ['value1', 'value2']}
>>> r2 = requests.post('https://httpbin.org/post', data=payload_dict)
>>> print(r1.text)
{
  ...
  "form": {
    "key1": [
      "value1",
      "value2"
    ]
  },
  ...
}
>>> r1.text == r2.text
True

Бывают случаи, когда нужно отправить данные не закодированные методом form-encoded. Если вы передадите в запрос строку вместо словаря, эти данные отправятся в не измененном виде.

К примеру, GitHub API v3 принимает закодированные JSON POST/PATCH данные:

import json

url = 'https://api.github.com/some/endpoint'  
payload = {'some': 'data'}  
r = requests.post(url, data=json.dumps(payload))  

Вместо того, чтобы кодировать dict, вы можете передать его напрямую, используя параметр json (добавленный в версии 2.4.2), и он будет автоматически закодирован:

url = 'https://api.github.com/some/endpoint'  
payload = {'some': 'data'}  
r = requests.post(url, json=payload) 

Обратите внимание, параметр json игнорируется, если передаются data или files.
Использование параметра json в запросе изменит заголовок Content-Type на application/json.

POST отправка Multipart-Encoded файла

Запросы упрощают загрузку файлов с многостраничным кодированием (Multipart-Encoded) :

>>> url = 'https://httpbin.org/post'
>>> files = {'file': open('report.xls', 'rb')}
>>> r = requests.post(url, files=files)
>>> r.text
{
  ...
  "files": {
    "file": ""
  },
  ...
}

Вы можете установить имя файла, content_type и заголовки в явном виде:

>>> url = 'https://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}
>>> r = requests.post(url, files=files)
>>> r.text
{
  ...
  "files": {
    "file": ""
  },
  ...
}

Можете отправить строки, которые будут приняты в виде файлов:

>>> url = 'https://httpbin.org/post'
>>> files = {'file': ('report.csv', 'some,data,to,sendnanother,row,to,sendn')}
>>> r = requests.post(url, files=files)
>>> r.text
{
  ...
  "files": {
    "file": "some,data,to,send\nanother,row,to,send\n"
  },
  ...
}

В случае, если вы отправляете очень большой файл как запрос multipart/form-data, возможно понадобиться отправить запрос потоком. По умолчанию, requests не поддерживает этого, но есть отдельный пакет, который это делает — requests-toolbelt. Ознакомьтесь с документацией toolbelt для получения более детальной информации о том, как им пользоваться.

Для отправки нескольких файлов в одном запросе, обратитесь к расширенной документации.

Предупреждение!
Настоятельно рекомендуется открывать файлы в бинарном режиме. Это связано с тем, что запросы могут пытаться предоставить для вас заголовок Content-Length, и если это значение будет установлено на количество байтов в файле будут возникать ошибки, при открытии файла в текстовом режиме.

Коды состояния ответа

Мы можем проверить код состояния ответа:

>>> r = requests.get('https://httpbin.org/get')
>>> r.status_code
200

У requests есть встроенный объект вывода кодов состояния:

>>> r.status_code == requests.codes.ok
True

Если мы сделали неудачный запрос (ошибка 4XX или 5XX), то можем вызвать исключение с помощью r.raise_for_status():

>>> bad_r = requests.get('https://httpbin.org/status/404')
>>> bad_r.status_code
404
>>> bad_r.raise_for_status()
Traceback (most recent call last):
  File "requests/models.py", line 832, in raise_for_status
    raise http_error
requests.exceptions.HTTPError: 404 Client Error

Но если status_code для r оказался 200, то когда мы вызываем raise_for_status() мы получаем:

>>> r.raise_for_status()
None

Заголовки ответов

Мы можем просматривать заголовки ответа сервера, используя словарь Python:

>>> r.headers
{
    'content-encoding': 'gzip',
    'transfer-encoding': 'chunked',
    'connection': 'close',
    'server': 'nginx/1.0.4',
    'x-runtime': '148ms',
    'etag': '"e1ca502697e5c9317743dc078f67693f"',
    'content-type': 'application/json'
}

Это словарь особого рода, он создан специально для HTTP заголовков. Согласно с RFC 7230, имена заголовков HTTP нечувствительны к регистру.

Теперь мы можем получить доступ к заголовкам с большим буквами или без, если захотим:

>>> r.headers['Content-Type']
'application/json'
>>> r.headers.get('content-type')
'application/json'

Cookies

Если в запросе есть cookies, вы сможете быстро получить к ним доступ:

>>> url = 'https://example.com/some/cookie/setting/url'
>>> r = requests.get(url)
>>> r.cookies['example_cookie_name']
'example_cookie_value'

Чтобы отправить собственные cookies на сервер, используйте параметр cookies:

>>> url = 'https://httpbin.org/cookies'
>>> cookies = dict(cookies_are='working')
>>> r = requests.get(url, cookies=cookies)
>>> r.text
'{"cookies": {"cookies_are": "working"}}'

Cookies возвращаются в RequestsCookieJar, который работает как dict, но также предлагает более полный интерфейс, подходящий для использования в нескольких доменах или путях. Словарь с cookie может также передаваться в запросы:

>>> jar = requests.cookies.RequestsCookieJar()
>>> jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
>>> jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
>>> url = 'https://httpbin.org/cookies'
>>> r = requests.get(url, cookies=jar)
>>> r.text
'{"cookies": {"tasty_cookie": "yum"}}'

Редиректы и история

По умолчанию Requests будет выполнять редиректы для всех HTTP глаголов, кроме HEAD.

Мы можем использовать свойство history объекта Response, чтобы отслеживать редиректы .

Список Response.history содержит объекты Response, которые были созданы для того, чтобы выполнить запрос. Список сортируется от более ранних, до более поздних ответов.

Например, GitHub перенаправляет все запросы HTTP на HTTPS:

>>> r = requests.get('https://github.com/')
>>> r.url
'https://github.com/'
>>> r.status_code
200
>>> r.history
[]

Если вы используете запросы GET, OPTIONS, POST, PUT, PATCH или DELETE, вы можете отключить обработку редиректа с помощью параметра allow_redirects:

>>> r = requests.get('https://github.com/', allow_redirects=False)
>>> r.status_code
301
>>> r.history
[]

Если вы используете HEAD, вы также можете включить редирект:

>>> r = requests.head('https://github.com/', allow_redirects=True)
>>> r.url
'https://github.com/'
>>> r.history
[]

Тайм-ауты

Вы можете сделать так, чтобы Requests прекратил ожидание ответа после определенного количества секунд с помощью параметра timeout.

Почти весь код должен использовать этот параметр в запросах. Несоблюдение этого требования может привести к зависанию вашей программы:

>>> requests.get('https://github.com/', timeout=0.001)
Traceback (most recent call last):
  File "", line 1, in 
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)

Timeout это не ограничение по времени полной загрузки ответа. Исключение возникает, если сервер не дал ответ за timeout секунд (точнее, если ни одного байта не было получено от основного сокета за timeout секунд).

Ошибки и исключения

В случае неполадок в сети (например, отказа DNS, отказа соединения и т.д.), Requests вызовет исключение ConnectionError.

Response.raise_for_status() вызовет HTTPError если в запросе HTTP возникнет статус код ошибки.

Если выйдет время запроса, вызывается исключение Timeout. Если запрос превышает заданное значение максимального количества редиректов, то вызывают исключение TooManyRedirects.

Все исключения, которые вызывает непосредственно Requests унаследованы от requests.exceptions.RequestException.

Тест на знание основ Requests

Какой из HTTP-запросов является правильным?

requests.post(url, data={‘key’:’value’})

requests.get(url, params={‘key’:’value’})

requests.post(url, params={‘key’:’value’})

Какое из утверждений верно?

response.json() возвращает содержимое ответа в виде объекта dict

response.text возвращает содержимое ответа в виде байтового объекта

response.content возвращает содержимое ответа в виде строкового объекта

response.json возвращает содержимое ответа в виде объекта dict

Как получить куки из ответа на запрос?

Что вернет метод status_code объекта Response?

Ошибку, если запрос неудачный

Какой код сформирует url «https://test.com/page?key1=value1&key2=value21,value22»

r.get(‘https://test.com/page’, params={‘key1’: ‘value1’, ‘key2’: [‘value21’, ‘value22’]})

r.get(‘https://test.com/page’, params={‘key1’: ‘value1’, ‘key2’: ‘value21,value22’})

r.get(‘https://test.com/page’, params={‘key1’: ‘value1’, ‘key2’: ‘value21’, ‘key2’: ‘value22’})

Библиотека requests является стандартным инструментом для составления HTTP-запросов в Python. Простой и аккуратный API значительно облегчает трудоемкий процесс создания запросов. Таким образом, можно сосредоточиться на взаимодействии со службами и использовании данных в приложении.

Содержание статьи

  • Python установка библиотеки requests
  • Python библиотека Requests метод GET
  • Объект Response получение ответа на запрос в Python
  • HTTP коды состояний
  • Получить содержимое страницы в Requests
  • HTTP заголовки в Requests
  • Python Requests параметры запроса
  • Настройка HTTP заголовка запроса (headers)
  • Примеры HTTP методов в Requests
  • Python Requests тело сообщения
  • Python Requests анализ запроса
  • Python Requests аутентификация HTTP AUTH
  • Python Requests проверка SSL сертификата
  • Python Requests производительность приложений
  • Объект Session в Requests
  • HTTPAdapter — Максимальное количество повторов запроса в Requests

В данной статье представлены наиболее полезные особенности requests. Показано, как изменить и приспособить requests к различным ситуациям, с которыми программисты сталкиваются чаще всего. Здесь также даются советы по эффективному использованию requests и предотвращению влияния сторонних служб, которые могут сильно замедлить работу используемого приложения. Мы использовали библиотек requests в уроке по парсингу html через библиотеку BeautifulSoup.

Ключевые аспекты инструкции:

  • Создание запросов при помощи самых популярных HTTP методов;
  • Редактирование заголовков запросов и данных при помощи строки запроса и содержимого сообщения;
  • Анализ данных запросов и откликов;
  • Создание авторизированных запросов;
  • Настройка запросов для предотвращения сбоев и замедления работы приложения.

В статье собран оптимальный набор информации, необходимый для понимания данных примеров и особенностей их использования. Информация представлена в доступной в форме. Тем не менее, стоит иметь в виду, что для оптимального разбора инструкции потребуются хотя бы базовые знания HTTP.

Далее будут показаны наиболее эффективные методы использования requests в разрабатываемом приложении.

Python установка библиотеки requests

Для начала работы потребуется установить библиотеку requests. Для этого используется следующая команда.

Есть вопросы по Python?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Telegram Чат & Канал

Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!

Паблик VK

Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!

Тем, кто для работы с пакетами Python, использует виртуальную среду Pipenv, необходимо использовать немного другую команду.

$ pipenv install requests

Сразу после установки requests можно полноценно использовать в приложении. Импорт requests производится следующим образом.

Таким образом, все подготовительные этапы для последующего использования requests завершены. Начинать изучение requests лучше всего с того, как сделать запрос GET.

Python библиотека Requests метод GET

Такие HTTP методы, как GET и POST, определяют, какие действия будут выполнены при создании HTTP запроса. Помимо GET и POST для этой задачи могут быть использованы некоторые другие методы. Далее они также будут описаны в руководстве.

GET является одним из самых популярных HTTP методов. Метод GET указывает на то, что происходит попытка извлечь данные из определенного ресурса. Для того, чтобы выполнить запрос GET, используется requests.get().

Для проверки работы команды будет выполнен запрос GET в отношении Root REST API на GitHub. Для указанного ниже URL вызывается метод get().

requests.get(‘https://api.github.com’)

<Response [200]>

Если никакие python ошибки не возникло, вас можно поздравить – первый запрос успешно выполнен. Далее будет рассмотрен ответ на данный запрос, который можно получить при помощи объекта Response.

Объект Response получение ответа на запрос в Python

Response представляет собой довольно мощный объект для анализа результатов запроса. В качестве примера будет использован предыдущий запрос, только на этот раз результат будет представлен в виде переменной. Таким образом, получится лучше изучить его атрибуты и особенности использования.

response = requests.get(‘https://api.github.com’)

В данном примере при помощи get() захватывается определенное значение, что является частью объекта Response, и помещается в переменную под названием response. Теперь можно использовать переменную response для того, чтобы изучить данные, которые были получены в результате запроса GET.

HTTP коды состояний

Самыми первыми данными, которые будут получены через Response, будут коды состояния. Коды состояния сообщают о статусе запроса.

Например, статус 200 OK значит, что запрос успешно выполнен. А вот статус 404 NOT FOUND говорит о том, что запрашиваемый ресурс не был найден. Существует множество других статусных кодов, которые могут сообщить важную информацию, связанную с запросом.

Используя .status_code, можно увидеть код состояния, который возвращается с сервера.

>>> response.status_code

200

.status_code вернул значение 200. Это значит, что запрос был выполнен успешно, а сервер ответил, отобразив запрашиваемую информацию.

В некоторых случаях необходимо использовать полученную информацию для написания программного кода.

if response.status_code == 200:

    print(‘Success!’)

elif response.status_code == 404:

    print(‘Not Found.’)

В таком случае, если с сервера будет получен код состояния 200, тогда программа выведет значение Success!. Однако, если от сервера поступит код 404, тогда программа выведет значение Not Found.
requests может значительно упростить весь процесс. Если использовать Response в условных конструкциях, то при получении кода состояния в промежутке от 200 до 400, будет выведено значение True. В противном случае отобразится значение False.

Последний пример можно упростить при помощи использования оператора if.

if response:

    print(‘Success!’)

else:

    print(‘An error has occurred.’)

Стоит иметь в виду, что данный способ не проверяет, имеет ли статусный код точное значение 200. Причина заключается в том, что другие коды в промежутке от 200 до 400, например, 204 NO CONTENT и 304 NOT MODIFIED, также считаются успешными в случае, если они могут предоставить действительный ответ.

К примеру, код состояния 204 говорит о том, что ответ успешно получен, однако в полученном объекте нет содержимого. Можно сказать, что для оптимально эффективного использования способа необходимо убедиться, что начальный запрос был успешно выполнен. Требуется изучить код состояния и в случае необходимости произвести необходимые поправки, которые будут зависеть от значения полученного кода.

Допустим, если при использовании оператора if вы не хотите проверять код состояния, можно расширить диапазон исключений для неудачных результатов запроса. Это можно сделать при помощи использования .raise_for_status().

import requests

from requests.exceptions import HTTPError

for url in [‘https://api.github.com’, ‘https://api.github.com/invalid’]:

    try:

        response = requests.get(url)

        # если ответ успешен, исключения задействованы не будут

        response.raise_for_status()

    except HTTPError as http_err:

        print(f‘HTTP error occurred: {http_err}’)  # Python 3.6

    except Exception as err:

        print(f‘Other error occurred: {err}’)  # Python 3.6

    else:

        print(‘Success!’)

В случае вызова исключений через .raise_for_status() к некоторым кодам состояния применяется HTTPError. Когда код состояния показывает, что запрос успешно выполнен, программа продолжает работу без применения политики исключений.

На заметку. Для более продуктивной работы в Python 3.6 будет не лишним изучить f-строки. Не стоит пренебрегать ими, так как это отличный способ упростить форматирование строк.

Анализ способов использования кодов состояния, полученных с сервера, является неплохим стартом для изучения requests. Тем не менее, при создании запроса GET, значение кода состояния является не самой важной информацией, которую хочет получить программист. Обычно запрос производится для извлечения более содержательной информации. В дальнейшем будет показано, как добраться до актуальных данных, которые сервер высылает отправителю в ответ на запрос.

Зачастую ответ на запрос GET содержит весьма ценную информацию. Она находится в теле сообщения и называется пейлоад (payload). Используя атрибуты и методы библиотеки Response, можно получить пейлоад в различных форматах.

Для того, чтобы получить содержимое запроса в байтах, необходимо использовать .content.

>>> response = requests.get(‘https://api.github.com’)

>>> response.content

b‘{«current_user_url»:»https://api.github.com/user»,»current_user_authorizations_html_url»:»https://github.com/settings/connections/applications{/client_id}»,»authorizations_url»:»https://api.github.com/authorizations»,»code_search_url»:»https://api.github.com/search/code?q={query}{&page,per_page,sort,order}»,»commit_search_url»:»https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}»,»emails_url»:»https://api.github.com/user/emails»,»emojis_url»:»https://api.github.com/emojis»,»events_url»:»https://api.github.com/events»,»feeds_url»:»https://api.github.com/feeds»,»followers_url»:»https://api.github.com/user/followers»,»following_url»:»https://api.github.com/user/following{/target}»,»gists_url»:»https://api.github.com/gists{/gist_id}»,»hub_url»:»https://api.github.com/hub»,»issue_search_url»:»https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}»,»issues_url»:»https://api.github.com/issues»,»keys_url»:»https://api.github.com/user/keys»,»notifications_url»:»https://api.github.com/notifications»,»organization_repositories_url»:»https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}»,»organization_url»:»https://api.github.com/orgs/{org}»,»public_gists_url»:»https://api.github.com/gists/public»,»rate_limit_url»:»https://api.github.com/rate_limit»,»repository_url»:»https://api.github.com/repos/{owner}/{repo}»,»repository_search_url»:»https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}»,»current_user_repositories_url»:»https://api.github.com/user/repos{?type,page,per_page,sort}»,»starred_url»:»https://api.github.com/user/starred{/owner}{/repo}»,»starred_gists_url»:»https://api.github.com/gists/starred»,»team_url»:»https://api.github.com/teams»,»user_url»:»https://api.github.com/users/{user}»,»user_organizations_url»:»https://api.github.com/user/orgs»,»user_repositories_url»:»https://api.github.com/users/{user}/repos{?type,page,per_page,sort}»,»user_search_url»:»https://api.github.com/search/users?q={query}{&page,per_page,sort,order}»}’

Использование .content обеспечивает доступ к чистым байтам ответного пейлоада, то есть к любым данным в теле запроса. Однако, зачастую требуется конвертировать полученную информацию в строку в кодировке UTF-8. response делает это при помощи .text.

>>> response.text

‘{«current_user_url»:»https://api.github.com/user»,»current_user_authorizations_html_url»:»https://github.com/settings/connections/applications{/client_id}»,»authorizations_url»:»https://api.github.com/authorizations»,»code_search_url»:»https://api.github.com/search/code?q={query}{&page,per_page,sort,order}»,»commit_search_url»:»https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}»,»emails_url»:»https://api.github.com/user/emails»,»emojis_url»:»https://api.github.com/emojis»,»events_url»:»https://api.github.com/events»,»feeds_url»:»https://api.github.com/feeds»,»followers_url»:»https://api.github.com/user/followers»,»following_url»:»https://api.github.com/user/following{/target}»,»gists_url»:»https://api.github.com/gists{/gist_id}»,»hub_url»:»https://api.github.com/hub»,»issue_search_url»:»https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}»,»issues_url»:»https://api.github.com/issues»,»keys_url»:»https://api.github.com/user/keys»,»notifications_url»:»https://api.github.com/notifications»,»organization_repositories_url»:»https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}»,»organization_url»:»https://api.github.com/orgs/{org}»,»public_gists_url»:»https://api.github.com/gists/public»,»rate_limit_url»:»https://api.github.com/rate_limit»,»repository_url»:»https://api.github.com/repos/{owner}/{repo}»,»repository_search_url»:»https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}»,»current_user_repositories_url»:»https://api.github.com/user/repos{?type,page,per_page,sort}»,»starred_url»:»https://api.github.com/user/starred{/owner}{/repo}»,»starred_gists_url»:»https://api.github.com/gists/starred»,»team_url»:»https://api.github.com/teams»,»user_url»:»https://api.github.com/users/{user}»,»user_organizations_url»:»https://api.github.com/user/orgs»,»user_repositories_url»:»https://api.github.com/users/{user}/repos{?type,page,per_page,sort}»,»user_search_url»:»https://api.github.com/search/users?q={query}{&page,per_page,sort,order}»}’

Декодирование байтов в строку требует наличия определенной модели кодировки. По умолчанию requests попытается узнать текущую кодировку, ориентируясь по заголовкам HTTP. Указать необходимую кодировку можно при помощи добавления .encoding перед .text.

>>> response.encoding = ‘utf-8’ # Optional: requests infers this internally

>>> response.text

‘{«current_user_url»:»https://api.github.com/user»,»current_user_authorizations_html_url»:»https://github.com/settings/connections/applications{/client_id}»,»authorizations_url»:»https://api.github.com/authorizations»,»code_search_url»:»https://api.github.com/search/code?q={query}{&page,per_page,sort,order}»,»commit_search_url»:»https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}»,»emails_url»:»https://api.github.com/user/emails»,»emojis_url»:»https://api.github.com/emojis»,»events_url»:»https://api.github.com/events»,»feeds_url»:»https://api.github.com/feeds»,»followers_url»:»https://api.github.com/user/followers»,»following_url»:»https://api.github.com/user/following{/target}»,»gists_url»:»https://api.github.com/gists{/gist_id}»,»hub_url»:»https://api.github.com/hub»,»issue_search_url»:»https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}»,»issues_url»:»https://api.github.com/issues»,»keys_url»:»https://api.github.com/user/keys»,»notifications_url»:»https://api.github.com/notifications»,»organization_repositories_url»:»https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}»,»organization_url»:»https://api.github.com/orgs/{org}»,»public_gists_url»:»https://api.github.com/gists/public»,»rate_limit_url»:»https://api.github.com/rate_limit»,»repository_url»:»https://api.github.com/repos/{owner}/{repo}»,»repository_search_url»:»https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}»,»current_user_repositories_url»:»https://api.github.com/user/repos{?type,page,per_page,sort}»,»starred_url»:»https://api.github.com/user/starred{/owner}{/repo}»,»starred_gists_url»:»https://api.github.com/gists/starred»,»team_url»:»https://api.github.com/teams»,»user_url»:»https://api.github.com/users/{user}»,»user_organizations_url»:»https://api.github.com/user/orgs»,»user_repositories_url»:»https://api.github.com/users/{user}/repos{?type,page,per_page,sort}»,»user_search_url»:»https://api.github.com/search/users?q={query}{&page,per_page,sort,order}»}’

Если присмотреться к ответу, можно заметить, что его содержимое является сериализированным JSON контентом. Воспользовавшись словарем, можно взять полученные из .text строки str и провести с ними обратную сериализацию при помощи использования json.loads(). Есть и более простой способ, который требует применения .json().

>>> response.json()

{‘current_user_url’: ‘https://api.github.com/user’, ‘current_user_authorizations_html_url’: ‘https://github.com/settings/connections/applications{/client_id}’, ‘authorizations_url’: ‘https://api.github.com/authorizations’, ‘code_search_url’: ‘https://api.github.com/search/code?q={query}{&page,per_page,sort,order}’, ‘commit_search_url’: ‘https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}’, ’emails_url’: ‘https://api.github.com/user/emails’, ’emojis_url’: ‘https://api.github.com/emojis’, ‘events_url’: ‘https://api.github.com/events’, ‘feeds_url’: ‘https://api.github.com/feeds’, ‘followers_url’: ‘https://api.github.com/user/followers’, ‘following_url’: ‘https://api.github.com/user/following{/target}’, ‘gists_url’: ‘https://api.github.com/gists{/gist_id}’, ‘hub_url’: ‘https://api.github.com/hub’, ‘issue_search_url’: ‘https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}’, ‘issues_url’: ‘https://api.github.com/issues’, ‘keys_url’: ‘https://api.github.com/user/keys’, ‘notifications_url’: ‘https://api.github.com/notifications’, ‘organization_repositories_url’: ‘https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}’, ‘organization_url’: ‘https://api.github.com/orgs/{org}’, ‘public_gists_url’: ‘https://api.github.com/gists/public’, ‘rate_limit_url’: ‘https://api.github.com/rate_limit’, ‘repository_url’: ‘https://api.github.com/repos/{owner}/{repo}’, ‘repository_search_url’: ‘https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}’, ‘current_user_repositories_url’: ‘https://api.github.com/user/repos{?type,page,per_page,sort}’, ‘starred_url’: ‘https://api.github.com/user/starred{/owner}{/repo}’, ‘starred_gists_url’: ‘https://api.github.com/gists/starred’, ‘team_url’: ‘https://api.github.com/teams’, ‘user_url’: ‘https://api.github.com/users/{user}’, ‘user_organizations_url’: ‘https://api.github.com/user/orgs’, ‘user_repositories_url’: ‘https://api.github.com/users/{user}/repos{?type,page,per_page,sort}’, ‘user_search_url’: ‘https://api.github.com/search/users?q={query}{&page,per_page,sort,order}’}

Тип полученного значения из .json(), является словарем. Это значит, что доступ к его содержимому можно получить по ключу.

Коды состояния и тело сообщения предоставляют огромный диапазон возможностей. Однако, для их оптимального использования требуется изучить метаданные и заголовки HTTP.

HTTP заголовки ответов на запрос могут предоставить определенную полезную информацию. Это может быть тип содержимого ответного пейлоада, а также ограничение по времени для кеширования ответа. Для просмотра HTTP заголовков загляните в атрибут .headers.

>>> response.headers

{‘Server’: ‘GitHub.com’, ‘Date’: ‘Mon, 10 Dec 2018 17:49:54 GMT’, ‘Content-Type’: ‘application/json; charset=utf-8’, ‘Transfer-Encoding’: ‘chunked’, ‘Status’: ‘200 OK’, ‘X-RateLimit-Limit’: ’60’, ‘X-RateLimit-Remaining’: ’59’, ‘X-RateLimit-Reset’: ‘1544467794’, ‘Cache-Control’: ‘public, max-age=60, s-maxage=60’, ‘Vary’: ‘Accept’, ‘ETag’: ‘W/»7dc470913f1fe9bb6c7355b50a0737bc»‘, ‘X-GitHub-Media-Type’: ‘github.v3; format=json’, ‘Access-Control-Expose-Headers’: ‘ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type’, ‘Access-Control-Allow-Origin’: ‘*’, ‘Strict-Transport-Security’: ‘max-age=31536000; includeSubdomains; preload’, ‘X-Frame-Options’: ‘deny’, ‘X-Content-Type-Options’: ‘nosniff’, ‘X-XSS-Protection’: ‘1; mode=block’, ‘Referrer-Policy’: ‘origin-when-cross-origin, strict-origin-when-cross-origin’, ‘Content-Security-Policy’: «default-src ‘none'», ‘Content-Encoding’: ‘gzip’, ‘X-GitHub-Request-Id’: ‘E439:4581:CF2351:1CA3E06:5C0EA741’}

.headers возвращает словарь, что позволяет получить доступ к значению заголовка HTTP по ключу. Например, для просмотра типа содержимого ответного пейлоада, требуется использовать Content-Type.

>>> response.headers[‘Content-Type’]

‘application/json; charset=utf-8’

У объектов словарей в качестве заголовков есть своим особенности. Специфика HTTP предполагает, что заголовки не чувствительны к регистру. Это значит, что при получении доступа к заголовкам можно не беспокоится о том, использованы строчным или прописные буквы.

>>> response.headers[‘content-type’]

‘application/json; charset=utf-8’

При использовании ключей 'content-type' и 'Content-Type' результат будет получен один и тот же.

Это была основная информация, требуемая для работы с Response. Были задействованы главные атрибуты и методы, а также представлены примеры их использования. В дальнейшем будет показано, как изменится ответ после настройки запроса GET.

Python Requests параметры запроса

Наиболее простым способом настроить запрос GET является передача значений через параметры строки запроса в URL. При использовании метода get(), данные передаются в params. Например, для того, чтобы посмотреть на библиотеку requests можно использовать Search API на GitHub.

import requests

# Поиск местонахождения для запросов на GitHub

response = requests.get(

    ‘https://api.github.com/search/repositories’,

    params={‘q’: ‘requests+language:python’},

)

# Анализ некоторых атрибутов местонахождения запросов

json_response = response.json()

repository = json_response[‘items’][0]

print(f‘Repository name: {repository[«name»]}’)  # Python 3.6+

print(f‘Repository description: {repository[«description»]}’)  # Python 3.6+

Передавая словарь {'q': 'requests+language:python'} в параметр params, который является частью .get(), можно изменить ответ, что был получен при использовании Search API.

Можно передать параметры в get() в форме словаря, как было показано выше. Также можно использовать список кортежей.

>>> requests.get(

...     ‘https://api.github.com/search/repositories’,

...     params=[(‘q’, ‘requests+language:python’)],

... )

<Response [200]>

Также можно передать значение в байтах.

>>> requests.get(

...     ‘https://api.github.com/search/repositories’,

...     params=b‘q=requests+language:python’,

... )

<Response [200]>

Строки запроса полезны для уточнения параметров в запросах GET. Также можно настроить запросы при помощи добавления или изменения заголовков отправленных сообщений.

Для изменения HTTP заголовка требуется передать словарь данного HTTP заголовка в get() при помощи использования параметра headers. Например, можно изменить предыдущий поисковой запрос, подсветив совпадения в результате. Для этого в заголовке Accept медиа тип уточняется при помощи text-match.

import requests

response = requests.get(

    ‘https://api.github.com/search/repositories’,

    params={‘q’: ‘requests+language:python’},

    headers={‘Accept’: ‘application/vnd.github.v3.text-match+json’},

)

# просмотр нового массива `text-matches` с предоставленными данными

# о поиске в пределах результатов

json_response = response.json()

repository = json_response[‘items’][0]

print(f‘Text matches: {repository[«text_matches»]}’)

Заголовок Accept сообщает серверу о типах контента, который можно использовать в рассматриваемом приложении. Здесь подразумевается, что все совпадения будут подсвечены, для чего в заголовке используется значение application/vnd.github.v3.text-match+json. Это уникальный заголовок Accept для GitHub. В данном случае содержимое представлено в специальном JSON формате.

Перед более глубоким изучением способов редактирования запросов, будет не лишним остановиться на некоторых других методах HTTP.

Примеры HTTP методов в Requests

Помимо GET, большой популярностью пользуются такие методы, как POST, PUT, DELETE, HEAD, PATCH и OPTIONS. Для каждого из этих методов существует своя сигнатура, которая очень похожа на метод get().

>>> requests.post(‘https://httpbin.org/post’, data={‘key’:‘value’})

>>> requests.put(‘https://httpbin.org/put’, data={‘key’:‘value’})

>>> requests.delete(‘https://httpbin.org/delete’)

>>> requests.head(‘https://httpbin.org/get’)

>>> requests.patch(‘https://httpbin.org/patch’, data={‘key’:‘value’})

>>> requests.options(‘https://httpbin.org/get’)

Каждая функция создает запрос к httpbin сервису, используя при этом ответный HTTP метод. Результат каждого метода можно изучить способом, который был использован в предыдущих примерах.

>>> response = requests.head(‘https://httpbin.org/get’)

>>> response.headers[‘Content-Type’]

‘application/json’

>>> response = requests.delete(‘https://httpbin.org/delete’)

>>> json_response = response.json()

>>> json_response[‘args’]

{}

При использовании каждого из данных методов в Response могут быть возвращены заголовки, тело запроса, коды состояния и многие другие аспекты. Методы POST, PUT и PATCH в дальнейшем будут описаны более подробно.

Python Requests тело сообщения

В соответствии со спецификацией HTTP запросы POST, PUT и PATCH передают информацию через тело сообщения, а не через параметры строки запроса. Используя requests, можно передать данные в параметр data.

В свою очередь data использует словарь, список кортежей, байтов или объект файла. Это особенно важно, так как может возникнуть необходимость адаптации отправляемых с запросом данных в соответствии с определенными параметрами сервера.

К примеру, если тип содержимого запроса application/x-www-form-urlencoded, можно отправить данные формы в виде словаря.

>>> requests.post(‘https://httpbin.org/post’, data={‘key’:‘value’})

<Response [200]>

Ту же самую информацию также можно отправить в виде списка кортежей.

>>> requests.post(‘https://httpbin.org/post’, data=[(‘key’, ‘value’)])

<Response [200]>

В том случае, если требуется отравить данные JSON, можно использовать параметр json. При передачи данных JSON через json, requests произведет сериализацию данных и добавит правильный Content-Type заголовок.

Стоит взять на заметку сайт httpbin.org. Это чрезвычайно полезный ресурс, созданный человеком, который внедрил использование requests – Кеннетом Рейтцом. Данный сервис предназначен для тестовых запросов. Здесь можно составить пробный запрос и получить ответ с требуемой информацией. В качестве примера рассмотрим базовый запрос с использованием POST.

>>> response = requests.post(‘https://httpbin.org/post’, json={‘key’:‘value’})

>>> json_response = response.json()

>>> json_response[‘data’]

‘{«key»: «value»}’

>>> json_response[‘headers’][‘Content-Type’]

‘application/json’

Здесь видно, что сервер получил данные и HTTP заголовки, отправленные вместе с запросом. requests также предоставляет информацию в форме PreparedRequest.

Python Requests анализ запроса

При составлении запроса стоит иметь в виду, что перед его фактической отправкой на целевой сервер библиотека requests выполняет определенную подготовку. Подготовка запроса включает в себя такие вещи, как проверка заголовков и сериализация содержимого JSON.

Если открыть .request, можно просмотреть PreparedRequest.

>>> response = requests.post(‘https://httpbin.org/post’, json={‘key’:‘value’})

>>> response.request.headers[‘Content-Type’]

‘application/json’

>>> response.request.url

‘https://httpbin.org/post’

>>> response.request.body

b‘{«key»: «value»}’

Проверка PreparedRequest открывает доступ ко всей информации о выполняемом запросе. Это может быть пейлоад, URL, заголовки, аутентификация и многое другое.

У всех описанных ранее типов запросов была одна общая черта – они представляли собой неаутентифицированные запросы к публичным API. Однако, подобающее большинство служб, с которыми может столкнуться пользователь, запрашивают аутентификацию.

Python Requests аутентификация HTTP AUTH

Аутентификация помогает сервису понять, кто вы. Как правило, вы предоставляете свои учетные данные на сервер, передавая данные через заголовок Authorization или пользовательский заголовок, определенной службы. Все функции запроса, которые вы видели до этого момента, предоставляют параметр с именем auth, который позволяет вам передавать свои учетные данные.

Одним из примеров API, который требует аутентификации, является Authenticated User API на GitHub. Это конечная точка веб-сервиса, которая предоставляет информацию о профиле аутентифицированного пользователя. Чтобы отправить запрос API-интерфейсу аутентифицированного пользователя, вы можете передать свое имя пользователя и пароль на GitHub через кортеж в get().

>>> from getpass import getpass

>>> requests.get(‘https://api.github.com/user’, auth=(‘username’, getpass()))

<Response [200]>

Запрос выполнен успешно, если учетные данные, которые вы передали в кортеже auth, действительны. Если вы попытаетесь сделать этот запрос без учетных данных, вы увидите, что код состояния 401 Unauthorized.

>>> requests.get(‘https://api.github.com/user’)

<Response [401]>

Когда вы передаете имя пользователя и пароль в кортеже параметру auth, вы используете учетные данные при помощи базовой схемы аутентификации HTTP.

Таким образом, вы можете создать тот же запрос, передав подробные учетные данные базовой аутентификации, используя HTTPBasicAuth.

>>> from requests.auth import HTTPBasicAuth

>>> from getpass import getpass

>>> requests.get(

...     ‘https://api.github.com/user’,

...     auth=HTTPBasicAuth(‘username’, getpass())

... )

<Response [200]>

Хотя вам не нужно явно указывать обычную аутентификацию, может потребоваться аутентификация с использованием другого метода. requests предоставляет другие методы аутентификации, например, HTTPDigestAuth и HTTPProxyAuth.

Вы даже можете предоставить свой собственный механизм аутентификации. Для этого необходимо сначала создать подкласс AuthBase. Затем происходит имплементация __call__().

import requests

from requests.auth import AuthBase

class TokenAuth(AuthBase):

    «»»Implements a custom authentication scheme.»»»

    def __init__(self, token):

        self.token = token

    def __call__(self, r):

        «»»Attach an API token to a custom auth header.»»»

        r.headers[‘X-TokenAuth’] = f‘{self.token}’  # Python 3.6+

        return r

requests.get(‘https://httpbin.org/get’, auth=TokenAuth(‘12345abcde-token’))

Здесь пользовательский механизм TokenAuth получает специальный токен. Затем этот токен включается заголовок X-TokenAuth запроса.

Плохие механизмы аутентификации могут привести к уязвимостям безопасности, поэтому, если службе по какой-то причине не нужен настраиваемый механизм аутентификации, вы всегда можете использовать проверенную схему аутентификации, такую как Basic или OAuth.

Пока вы думаете о безопасности, давайте рассмотрим использование requests в SSL сертификатах.

Python Requests проверка SSL сертификата

Всякий раз, когда данные, которые вы пытаетесь отправить или получить, являются конфиденциальными, безопасность важна. Вы общаетесь с защищенными сайтами через HTTP, устанавливая зашифрованное соединение с использованием SSL, что означает, что проверка SSL сертификата целевого сервера имеет решающее значение.

Хорошей новостью является то, что requests по умолчанию все делает сам. Однако в некоторых случаях необходимо внести определенные поправки.

Если требуется отключить проверку SSL-сертификата, параметру verify функции запроса можно присвоить значение False.

>>> requests.get(‘https://api.github.com’, verify=False)

InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advancedusage.html#ssl-warnings

  InsecureRequestWarning)

<Response [200]>

В случае небезопасного запроса requests предупреждает о возможности потери информации и просит сохранить данные или отказаться от запроса.

Примечание. Для предоставления сертификатов requests использует пакет, который вызывается certifi. Это дает понять requests, каким ответам можно доверять. Поэтому вам следует часто обновлять certifi, чтобы обеспечить максимальную безопасность ваших соединений.

Python Requests производительность приложений

При использовании requests, особенно в среде приложений, важно учитывать влияние на производительность. Такие функции, как контроль таймаута, сеансы и ограничения повторных попыток, могут помочь обеспечить бесперебойную работу приложения.

Таймауты

Когда вы отправляете встроенный запрос во внешнюю службу, вашей системе нужно будет дождаться ответа, прежде чем двигаться дальше. Если ваше приложение слишком долго ожидает ответа, запросы к службе могут быть сохранены, пользовательский интерфейс может пострадать или фоновые задания могут зависнуть.

По умолчанию в requests на ответ время не ограничено, и весь процесс может занять значительный промежуток. По этой причине вы всегда должны указывать время ожидания, чтобы такого не происходило. Чтобы установить время ожидания запроса, используйте параметр timeout. timeout может быть целым числом или числом с плавающей точкой, представляющим количество секунд ожидания ответа до истечения времени ожидания.

>>> requests.get(‘https://api.github.com’, timeout=1)

<Response [200]>

>>> requests.get(‘https://api.github.com’, timeout=3.05)

<Response [200]>

В первом примере запрос истекает через 1 секунду. Во втором примере запрос истекает через 3,05 секунды.

Вы также можете передать кортеж. Это – таймаут соединения (время, за которое клиент может установить соединение с сервером), а второй – таймаут чтения (время ожидания ответа, как только ваш клиент установил соединение):

>>> requests.get(‘https://api.github.com’, timeout=(2, 5))

<Response [200]>

Если запрос устанавливает соединение в течение 2 секунд и получает данные в течение 5 секунд после установления соединения, то ответ будет возвращен, как это было раньше. Если время ожидания истекло, функция вызовет исключение Timeout.

import requests

from requests.exceptions import Timeout

try:

    response = requests.get(‘https://api.github.com’, timeout=1)

except Timeout:

    print(‘The request timed out’)

else:

    print(‘The request did not time out’)

Ваша программа может поймать исключение Timeout и ответить соответственно.

Объект Session в Requests

До сих пор вы имели дело с requests API высокого уровня, такими как get() и post(). Эти функции являются абстракцией того, что происходит, когда вы делаете свои запросы. Они скрывают детали реализации, такие как управление соединениями, так что вам не нужно о них беспокоиться.

Под этими абстракциями находится класс под названием Session. Если вам необходимо настроить контроль над выполнением запросов или повысить производительность ваших запросов, вам может потребоваться использовать Session напрямую.

Сессии используются для сохранения параметров в запросах.

Например, если вы хотите использовать одну и ту же аутентификацию для нескольких запросов, вы можете использовать сеанс:

import requests

from getpass import getpass

# используя менеджер контента, можно убедиться, что ресурсы, применимые

# во время сессии будут свободны после использования

with requests.Session() as session:

    session.auth = (‘username’, getpass())

    # Instead of requests.get(), you’ll use session.get()

    response = session.get(‘https://api.github.com/user’)

# здесь можно изучить ответ

print(response.headers)

print(response.json())

Каждый раз, когда вы делаете запрос session, после того как он был инициализирован с учетными данными аутентификации, учетные данные будут сохраняться.

Первичная оптимизация производительности сеансов происходит в форме постоянных соединений. Когда ваше приложение устанавливает соединение с сервером с помощью Session, оно сохраняет это соединение в пуле соединений. Когда ваше приложение снова хочет подключиться к тому же серверу, оно будет использовать соединение из пула, а не устанавливать новое.

HTTPAdapter — Максимальное количество повторов запроса в Requests

В случае сбоя запроса возникает необходимость сделать повторный запрос. Однако requests не будет делать это самостоятельно. Для применения функции повторного запроса требуется реализовать собственный транспортный адаптер.

Транспортные адаптеры позволяют определить набор конфигураций для каждой службы, с которой вы взаимодействуете. Предположим, вы хотите, чтобы все запросы к https://api.github.com были повторены три раза, прежде чем, наконец, появится ConnectionError. Для этого нужно построить транспортный адаптер, установить его параметр max_retries и подключить его к существующему объекту Session.

import requests

from requests.adapters import HTTPAdapter

from requests.exceptions import ConnectionError

github_adapter = HTTPAdapter(max_retries=3)

session = requests.Session()

# использование `github_adapter` для всех запросов, которые начинаются с указанным URL

session.mount(‘https://api.github.com’, github_adapter)

try:

    session.get(‘https://api.github.com’)

except ConnectionError as ce:

    print(ce)

При установке HTTPAdapter, github_adapter к session, session будет придерживаться своей конфигурации для каждого запроса к https://api.github.com.

Таймауты, транспортные адаптеры и сессии предназначены для обеспечения эффективности используемого кода и стабильности приложения.

Заключение

Изучение библиотеки Python requests является очень трудоемким процессом.

После разбора данных в статье примеров можно научиться тому, как:

  • Создавать запросы, используя различные методы HTTPGET, POST и PUT;
  • Настраивать свои запросы, изменив заголовки, аутентификацию, строки запросов и тела сообщений;
  • Проверять данные, которые были отправлены на сервер, а также те данные, которые сервер отправил обратно;
  • Работать с проверкой SSL сертификата;
  • Эффективно использовать requests, max_retries, timeout, Sessions и транспортные адаптеры.

Грамотное использование requests позволит наиболее эффективно настроить разрабатываемые приложения, исследуя широкий спектр веб-сервисов и данных, опубликованных на них.

  • Данная статья является переводом статьи: Python’s Requests Library (Guide)
  • Изображение статьи принадлежит сайту © RealPython

Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.

E-mail: vasile.buldumac@ati.utm.md

Образование
Universitatea Tehnică a Moldovei (utm.md)

  • 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»

Понравилась статья? Поделить с друзьями:
  • Request parameters error
  • Request parameter error перевод
  • Request parameter error xiaomi
  • Request parameter error mi unlock что делать
  • Request on sudrf block как исправить