dlang-requests — типа python-requests, только для D (часть 2)

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

    Во второй части статьи опишу использование библиотеки 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

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


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


      А если не 200, а 201?
        0
        Думаю в этом случае стоит кидать исключение, чтобы не требовать каждый раз проверять «а не пришёл ли 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 и возможно есть смысл вообще отказаться от мониторинга библиотекой кода ответа для запроса со стримингом. В любом случае заинтересованный пользователь имеет возможность узнать с каким кодом ответа приходит поток.
          0
          Исключение при превышении maxRedirects уже залито в гитхаб.
          А про стриминг только при коде 200 я ошибся — он работает при любом коде если есть тело ответа.

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

        Самое читаемое