Доброго времени суток!

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



Request и Response


На более низком уровне библиотеки находится структура Request, обеспечивающая всю функциональность библиотеки.

Request среди прочих методов имеет мeтоды get и post, которыми пользуются getContent() и postContent(), описанные в первой части. Параметры их вызова совпадают с параметрами для getContent() и postContent(). Зачем же она нужна, эта структура?

Для начала опишу как через ее методы можно управлять выполнением запросов.
  • verbosity — установка в значения 0,1,2 позволяет увеличить детальность вывода в stdout.
    Пример увеличения verbosity
    import std.stdio;
    import requests;
    
    void main()
    {
        auto rq = Request();
        rq.verbosity = 2;
        auto rs = rq.get("http://httpbin.org/get", ["a":"b"]);
        writeln(rs.responseBody);
    }
    

    > GET /get?a=b HTTP/1.1
    > Connection: Keep-Alive
    > User-Agent: dlang-requests
    > Accept-Encoding: gzip, deflate
    > Host: httpbin.org
    >
    < HTTP/1.1 200 OK
    < server: nginx
    < date: Sat, 25 Jun 2016 13:37:20 GMT
    < content-type: application/json
    < content-length: 229
    < connection: keep-alive
    < access-control-allow-origin: *
    < access-control-allow-credentials: true
    < 229 bytes of body received
    >> Connect time: 143 ms and 666 ?s
    >> Request send time: 304 ?s
    >> Response recv time: 144 ms and 121 ?s
    {
      "args": {
        "a": "b"
      },
      "headers": {
        "Accept-Encoding": "gzip, deflate",
        "Host": "httpbin.org",
        "User-Agent": "dlang-requests"
      },
      "origin": "xxx.xxx.xxx.xxx",
      "url": "http://httpbin.org/get?a=b"
    }

  • timeout — устанавливает таймаут на операции ввода-вывода и установки соединения.
    Пример установки таймаута.
    auto rq = Request();
    rq.timeout = 30.seconds;


  • maxRedirects. Управляет допустимым количкством редиректов при выполнении запросов.
    Пример ограничения числа редиректов
    import std.stdio;
    import requests;
    
    void main()
    {
        auto rq = Request();
        rq.verbosity = 2;
        rq.maxRedirects = 2;
        auto rs = rq.get("https://httpbin.org/absolute-redirect/3");
        writeln(rs.code);
    }
    

    Вывод:
    > GET /absolute-redirect/3 HTTP/1.1
    > Connection: Keep-Alive
    > User-Agent: dlang-requests
    > Accept-Encoding: gzip, deflate
    > Host: httpbin.org
    >
    < HTTP/1.1 302 FOUND
    < server: nginx
    < date: Sat, 25 Jun 2016 13:54:16 GMT
    < content-type: text/html; charset=utf-8
    < content-length: 283
    < connection: keep-alive
    < location: http://httpbin.org/absolute-redirect/2
    < access-control-allow-origin: *
    < access-control-allow-credentials: true
    < 283 bytes of body received
    >> Connect time: 505 ms and 705 ?s
    >> Request send time: 247 ?s
    >> Response recv time: 145 ms and 626 ?s
    > GET /absolute-redirect/2 HTTP/1.1
    > Connection: Keep-Alive
    > User-Agent: dlang-requests
    > Accept-Encoding: gzip, deflate
    > Host: httpbin.org
    >
    < HTTP/1.1 302 FOUND
    < server: nginx
    < date: Sat, 25 Jun 2016 13:54:16 GMT
    < content-type: text/html; charset=utf-8
    < content-length: 283
    < connection: keep-alive
    < location: http://httpbin.org/absolute-redirect/1
    < access-control-allow-origin: *
    < access-control-allow-credentials: true
    < 283 bytes of body received
    >> Connect time: 135 ms and 621 ?s
    >> Request send time: 128 ?s
    >> Response recv time: 136 ms and 689 ?s
    > GET /absolute-redirect/1 HTTP/1.1
    > Connection: Keep-Alive
    > User-Agent: dlang-requests
    > Accept-Encoding: gzip, deflate
    > Host: httpbin.org
    >
    < HTTP/1.1 302 FOUND
    < server: nginx
    < date: Sat, 25 Jun 2016 13:54:16 GMT
    < content-type: text/html; charset=utf-8
    < content-length: 251
    < connection: keep-alive
    < location: http://httpbin.org/get
    < access-control-allow-origin: *
    < access-control-allow-credentials: true
    < 251 bytes of body received
    >> Connect time: 2 ?s
    >> Request send time: 140 ?s
    >> Response recv time: 136 ms and 279 ?s
    302
    


  • authenticator — позволяет управлять авторизацией в запросах.
    Пример
    import std.stdio;
    import requests;
    
    void main()
    {
        auto rq = Request();
        rq.verbosity = 2;
        rq.authenticator = new BasicAuthentication("user", "passwd");
        rs = rq.get("http://httpbin.org/basic-auth/user/passwd");
        assert(rs.code==200);
    }
    


  • bufferSize — устанавливает размер буфера чтения (в байтах).
  • proxy — позволяет установить прокси для запросов в форме
    http://host:port/


Content Streaming


Изящной фичей python-requests являeтся streaming — пользователь получает ответ от сервера не по окончанию, а в процессе приёма документа. Для приёма и обработки больших документов, такой метод может помочь сэкономить память. python-requests позволяет получать документ в виде итератора. Для D естественным было-бы использовать InputRange. В этом случае мы можем использовать ответ не только для получения данных, но и для прямого использования с алгоритмами, работающими с InputRange.

import std.stdio;
import std.format;
import requests;

void main()
{
    auto rq = Request();
    rq.useStreaming = true;
    rq.verbosity = 2;
    auto rs = rq.get("https://api.github.com/search/repositories?order=desc&sort=updated&q=language:D");
    if ( rs.code == 200 ) {
        auto stream = rs.receiveAsRange();
        while( !stream.empty ) {
            writefln("portion of %d bytes received".format(stream.front.length));
            stream.popFront;
        }
    }
}


вывод
> GET /search/repositories?order=desc&sort=updated&q=language:D HTTP/1.1
> Connection: Keep-Alive
> User-Agent: dlang-requests
> Accept-Encoding: gzip, deflate
> Host: api.github.com
>
< HTTP/1.1 200 OK
< server: GitHub.com
< date: Sat, 25 Jun 2016 15:45:28 GMT
< content-type: application/json; charset=utf-8
< transfer-encoding: chunked
< content-encoding: gzip
< x-github-request-id: B077660C:560B:7F2F21:576EA717
< 277 bytes of body received
< 1370 bytes of body received
portion of 751 bytes received
portion of 2988 bytes received
portion of 4632 bytes received
portion of 6002 bytes received
portion of 7474 bytes received
portion of 9106 bytes received
portion of 10246 bytes received
portion of 11356 bytes received
portion of 12290 bytes received
portion of 12870 bytes received
portion of 63904 bytes received


Здесь видно что типом элементов для stream будет массив байт. Поэтому в следующем коде подсчета символов-цифр требуется использование joiner:
import std.stdio;
import std.ascii;
import std.algorithm;
import requests;

void main()
{
    auto rq = Request();
    rq.useStreaming = true;
    auto stream = rq.get("https://api.github.com/search/repositories?order=desc&sort=updated&q=language:D").receiveAsRange;
    writeln(stream.joiner.filter!isDigit.count);
}

Кроме обработки больших документов «на лету», стриминг даёт самый простой способ для сохранения документов на диске.

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

Методы PUT/DELETE/HEAD...


Все перечисленные до сих пор методы, в конечном итоге, используют шаблонный метод Request.exec(method), который кроме шаблонного параметра, управляющего HTTP-методом, принимает все те комбинации параметров, которые были упомянуты ранее.

import std.stdio;
import std.ascii;
import std.range;
import std.algorithm;
import requests;

void main()
{
    auto rq = Request();
    rq.useStreaming = true;
    auto rs = rq.exec!"HEAD"("https://api.github.com/search/repositories?order=desc&sort=updated&q=language:D");
    rs.code.writeln;
    rs.responseHeaders.
        byKeyValue.
        take(5).
        each!(p=>writeln(p.key, ": ", p.value));
}

Вывод
200
x-frame-options: deny
cache-control: no-cache
x-xss-protection: 1; mode=block
vary: Accept-Encoding
content-type: application/json; charset=utf-8


Таким же образом можно вызывать любые HTTP методы.

На этом заканчиваю вторую часть статьи.

На всякий случай еще раз ссылка на страницу проекта на Github

Всем удачи и приятного программирования!
Поделиться с друзьями
-->

Комментарии (3)


  1. vintage
    28.06.2016 07:47

    maxRedirects. Управляет допустимым количкством редиректов при выполнении запросов.


    Думаю в этом случае стоит кидать исключение, чтобы не требовать каждый раз проверять "а не пришёл ли 3** код ответа?"
    Стриминг допустим не только для запросов GET, но и для любых запросов, которые возвращают документ вместе с кодом 200.


    А если не 200, а 201?


    1. ik62
      28.06.2016 10:48

      Думаю в этом случае стоит кидать исключение, чтобы не требовать каждый раз проверять «а не пришёл ли 3** код ответа?»

      Хороший вариант, есть смысл поправить. Сделал ишью в проекте.

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

      А если не 200, а 201?

      По идее 201 не обязательно возвращает тело документа, он скорее сообщает что документ создан. Вот что горится в RFC:

      The request has been fulfilled and resulted in a new resource being created. The newly created resource can be referenced by the URI(s) returned in the entity of the response, with the most specific URI for the resource given by a Location header field. The response SHOULD include an entity containing a list of resource characteristics and location(s) from which the user or user agent can choose the one most appropriate.

      Но в жизни бывает всякое, и возможно что кто-то предполагает большой ответ с кодом 201 и возможно есть смысл вообще отказаться от мониторинга библиотекой кода ответа для запроса со стримингом. В любом случае заинтересованный пользователь имеет возможность узнать с каким кодом ответа приходит поток.


    1. ik62
      30.06.2016 23:59

      Исключение при превышении maxRedirects уже залито в гитхаб.
      А про стриминг только при коде 200 я ошибся — он работает при любом коде если есть тело ответа.