GithubHelp home page GithubHelp logo

ikod / dlang-requests Goto Github PK

View Code? Open in Web Editor NEW
154.0 8.0 31.0 1.12 MB

dlang http client library inspired by python-requests

License: Boost Software License 1.0

D 99.29% Shell 0.08% Meson 0.63%
dlang http-client dlang-requests d

dlang-requests's Introduction

dlang-requests

Build Status DUB

Alt

HTTP client library, inspired by python-requests with goals:

  • small memory footprint
  • performance
  • simple, high level API
  • native D implementation

API docs: Wiki

Table of contents

Library configurations

This library doesn't use vibe-d but it can work with vibe-d sockets for network IO, so you can use requests in your vibe-d applications. To build vibe-d variant, with requests ver 2.x use

    "dependencies": {
        "requests:vibed": "~>2"
    }

For requests 1.x use subConfiguration:

"dependencies": {
    "requests": "~>1"
},
"subConfigurations": {
    "requests": "vibed"
}

Two levels of API

  • At the highest API level you interested only in retrieving or posting document content. Use it when you don't need to add headers, set timeouts, or change any other defaults, if you don't interested in result codes or any details of request and/or response. This level propose next calls: getContent, postContent, putContent and patchContent. What you receive is a Buffer, which you can use as range, but you can easily convert it to ubyte[] using .data property. These calls also have ByLine counterparts which will lazily receive response from server, split it on \n and convert it into InputRange of ubyte[] (so that something like getContentByLine("https://httpbin.org/stream/50").map!"cast(string)a".filter!(a => a.canFind("\"id\": 28")) should work.

  • At the next level we have Request structure, which encapsulate all details and settings required for http(s)/ftp transfer. Operating on Request instance you can change many aspects of interaction with http/ftp server. Most important API calls are Request.get(), Reuest.post or Request.exec!"method" and so on (you will find examples below). You will receive Response with all available details -document body, status code, headers, timings, etc.

Windows ssl notes

In case requests can't find opsn ssl library on Windows, here is several steps that can help:

  1. From the slproweb download latest Win32OpenSSL_Light installer binaries for Windows.
  2. Install it. Important: allow installer to install libraries in system folders.

See step-by-step instructions here.

Make a simple request

Making HTTP/HTTPS/FTP requests with dlang-requests is simple. First of all, install and import requests module:

import requests;

If you only need content of some webpage, you can use getContent():

auto content = getContent("http://httpbin.org/");

getContent() will fetch complete document to buffer and return this buffer to the caller(see Straming for lazy content loading). content can be converted to string, or can be used as range. For example, if you need to count lines in content, you can directly apply splitter() and count:

writeln(content.splitter('\n').count);

Count non-empty lines:

writeln(content.splitter('\n').filter!"a!=``".count);

Actually, the buffer is a ForwardRange with length and random access, so you can apply many algorithms directly to it. Or you can extract data in form of ubyte[], using data property:

ubyte[] data = content.data;

Request with parameters

dlang-requests proposes simple way to make a request with parameters. For example, you have to simulate a search query for person: name - person name, age - person age, and so on... You can pass all parameters to get using queryParams() helper:

auto content = getContent("http://httpbin.org/get", queryParams("name", "any name", "age", 42));

If you check httpbin response, you will see that server recognized all parameters:

{
  "args": {
    "age": "42",
    "name": "any name"
  },
  "headers": {
    "Accept-Encoding": "gzip, deflate",
    "Host": "httpbin.org",
    "User-Agent": "dlang-requests"
  },
  "origin": "xxx.xxx.xxx.xxx",
  "url": "http://httpbin.org/get?name=any name&age=42"
}

Or, you can pass dictionary:

auto content = getContent("http://httpbin.org/get", ["name": "any name", "age": "42"]);

Which gives you the same response.

If getContent() fails

getContent() (and any other API call) can throw the following exceptions:

  • ConnectError when it can't connect to document origin for some reason (can't resolve name, connection refused, ...)
  • TimeoutException when any single operation (connect, receive, send) timed out.
  • ErrnoException when received ErrnoException from any underlying call.
  • RequestException in some other cases.

Posting data to server

The easy way to post with dlang-requests is postContent(). There are several ways to post data to server:

  1. Post to web-form using application/x-www-form-urlencoded - for posting short data.
  2. Post to web-form using multipart/form-data - for large data and file uploads.
  3. Post data to server without forms.

Form-urlencode

Call postContent() in the same way as getContent() with parameters:

import std.stdio;
import requests;

void main() {
    auto content = postContent("http://httpbin.org/post", queryParams("name", "any name", "age", 42));
    writeln(content);
}

Output:

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "age": "42",
    "name": "any name"
  },
  "headers": {
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "22",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "dlang-requests"
  },
  "json": null,
  "origin": "xxx.xxx.xxx.xxx",
  "url": "http://httpbin.org/post"
}

Multipart form

Posting multipart forms requires MultipartForm structure to be prepared:

import std.stdio;
import std.conv;
import std.string;
import requests;

void main() {
    MultipartForm form;
    form.add(formData("name", "any name"));
    form.add(formData("age", to!string(42)));
    form.add(formData("raw data", "some bytes".dup.representation));
    auto content = postContent("http://httpbin.org/post", form);
    writeln("Output:");
    writeln(content);
}

Output:

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "age": "42",
    "name": "any name",
    "raw data": "some bytes"
  },
  "headers": {
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "332",
    "Content-Type": "multipart/form-data; boundary=e3beab0d-d240-4ec1-91bb-d47b08af5999",
    "Host": "httpbin.org",
    "User-Agent": "dlang-requests"
  },
  "json": null,
  "origin": "xxx.xxx.xxx.xxx",
  "url": "http://httpbin.org/post"
}

Here is an example of posting a file:

import std.stdio;
import std.conv;
import std.string;
import requests;

void main() {
    MultipartForm form;
    form.add(formData("file", File("test.txt", "rb"), ["filename":"test.txt", "Content-Type": "text/plain"]));
    form.add(formData("age", "42"));
    auto content = postContent("http://httpbin.org/post", form);

    writeln("Output:");
    writeln(content);
}

Output:

{
  "args": {},
  "data": "",
  "files": {
    "file": "this is test file\n"
  },
  "form": {
    "age": "42"
  },
  "headers": {
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "282",
    "Content-Type": "multipart/form-data; boundary=3fd7317f-7082-4d63-82e2-16cfeaa416b4",
    "Host": "httpbin.org",
    "User-Agent": "dlang-requests"
  },
  "json": null,
  "origin": "xxx.xxx.xxx.xxx",
  "url": "http://httpbin.org/post"
}

Posting raw data without forms

postContent() can post from InputRanges. For example, to post file content:

import std.stdio;
import requests;

void main() {
    auto f = File("test.txt", "rb");
    auto content = postContent("http://httpbin.org/post", f.byChunk(5), "application/binary");
    writeln("Output:");
    writeln(content);
}

Output:

{
  "args": {},
  "data": "this is test file\n",
  "files": {},
  "form": {},
  "headers": {
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "18",
    "Content-Type": "application/binary",
    "Host": "httpbin.org",
    "User-Agent": "dlang-requests"
  },
  "json": null,
  "origin": "xxx.xxx.xxx.xxx",
  "url": "http://httpbin.org/post"
}

Or, if you keep your data in memory, you can use something like this:

auto content = postContent("http://httpbin.org/post", "ABCDEFGH", "application/binary");

Those are all details about simple API with default request parameters. The next section will describe a lower-level interface through Request structure.

Request structure

When you need to configure request details (like timeouts and other limits, keep-alive, ssl properties), or response details (code, headers), you have to use Request and Response structures:

Request rq = Request();
Response rs = rq.get("https://httpbin.org/");
assert(rs.code==200);

By default Keep-Alive requests are used, so you can reuse the connection:

import std.stdio;
import requests;

void main()
{
    auto rq = Request();
    rq.verbosity = 2;
    auto rs = rq.get("http://httpbin.org/image/jpeg");
    writeln(rs.responseBody.length);
    rs = rq.get("http://httpbin.org/image/png");
    writeln(rs.responseBody.length);
}

In the latter case rq.get() will reuse previous connection to server. Request will automatically reopen connection when host, protocol or port change (so it is safe to send different requests through single instance of Request). It also recovers when server prematurely closes keep-alive connection. You can turn keepAlive off when needed:

rq.keepAlive = false;

For anything other than default, you can configure Request structure for keep-alive, redirects handling, to add/remove headers, set IO buffer size and maximum size of response headers and body.

For example, to authorize with basic authentication, use the following code (works both for HTTP and FTP URLs):

rq = Request();
rq.authenticator = new BasicAuthentication("user", "passwd");
rs = rq.get("http://httpbin.org/basic-auth/user/passwd");

Here is a short description of some Request options you can set:

name type meaning default
keepAlive bool request keepalive connection true
maxRedirects *) uint maximum redirect depth (0 to disable) 10
maxHeadersLength *) size_t max. acceptable response headers length 32KB
maxContentLength *) size_t max. acceptable content length 5MB
timeout *) Duration timeout on connect or data transfer 30.seconds
bufferSize size_t socket io buffer size 16KB
verbosity uint verbosity level (0, 1, 2 or 3) 0
proxy string url of the HTTP proxy null
addHeaders string[string] additional headers null
useStreaming bool receive data as lazy InputRange false
cookie Cookie[] cookies you will send to server null
authenticator Auth authenticatior null
bind string use local address whan connect null
socketFactory NetworkStream user-provided connection factory null

*) Throws exception when limit is reached.

Request properties that are read-only:

name type meaning
cookie Cookie[] cookie the server sent to us
contentLength long current document's content length or -1 if unknown
contentReceived long content received
Redirect and connection optimisations

Request keep results of Permanent redirections in small cache. It also keep map (schema,host,port) -> connection of opened connections, for subsequent usage.

Streaming server response

When you plan to receive something really large in response (file download) you don't want to receive gigabytes of content into the response buffer. With useStreaming, you can receive response from server as input range. Elements of the range are chunks of data (of type ubyte[]). contentLength and contentReceived can be used to monitor progress:

import std.stdio;
import requests;

void main()
{
    auto rq = Request();
    rq.useStreaming = true;
    rq.verbosity = 2;
    auto rs = rq.get("http://httpbin.org/image/jpeg");
    auto stream = rs.receiveAsRange();
    while(!stream.empty) {
        writefln("Received %d bytes, total received %d from document length %d", stream.front.length, rs.contentReceived, rs.contentLength);
        stream.popFront;
    }
}

Output:

> GET /image/jpeg 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: Thu, 09 Jun 2016 16:25:57 GMT
< content-type: image/jpeg
< content-length: 35588
< connection: keep-alive
< access-control-allow-origin: *
< access-control-allow-credentials: true
< 1232 bytes of body received
< 1448 bytes of body received
Received 2680 bytes, total received 2680 from document length 35588
Received 2896 bytes, total received 5576 from document length 35588
Received 2896 bytes, total received 8472 from document length 35588
Received 2896 bytes, total received 11368 from document length 35588
Received 1448 bytes, total received 12816 from document length 35588
Received 1448 bytes, total received 14264 from document length 35588
Received 1448 bytes, total received 15712 from document length 35588
Received 2896 bytes, total received 18608 from document length 35588
Received 2896 bytes, total received 21504 from document length 35588
Received 2896 bytes, total received 24400 from document length 35588
Received 1448 bytes, total received 25848 from document length 35588
Received 2896 bytes, total received 28744 from document length 35588
Received 2896 bytes, total received 31640 from document length 35588
Received 2896 bytes, total received 34536 from document length 35588
Received 1052 bytes, total received 35588 from document length 35588

With verbosity >= 3, you will also receive a dump of each data portion received from server:

00000  48 54 54 50 2F 31 2E 31  20 32 30 30 20 4F 4B 0D  |HTTP/1.1 200 OK.|
00010  0A 53 65 72 76 65 72 3A  20 6E 67 69 6E 78 0D 0A  |.Server: nginx..|
00020  44 61 74 65 3A 20 53 75  6E 2C 20 32 36 20 4A 75  |Date: Sun, 26 Ju|
00030  6E 20 32 30 31 36 20 31  36 3A 31 36 3A 30 30 20  |n 2016 16:16:00 |
00040  47 4D 54 0D 0A 43 6F 6E  74 65 6E 74 2D 54 79 70  |GMT..Content-Typ|
00050  65 3A 20 61 70 70 6C 69  63 61 74 69 6F 6E 2F 6A  |e: application/j|
00060  73 6F 6E 0D 0A 54 72 61  6E 73 66 65 72 2D 45 6E  |son..Transfer-En|
00070  63 6F 64 69 6E 67 3A 20  63 68 75 6E 6B 65 64 0D  |coding: chunked.|
00080  0A 43 6F 6E 6E 65 63 74  69 6F 6E 3A 20 6B 65 65  |.Connection: kee|
...

Just for fun: with streaming you can forward content between servers in just two code lines. postContent will automatically receive next data portion from source and send it to destination:

import requests;                                                                                                            
import std.stdio;                                                                                                           
                                                                                                                            
void main()                                                                                                                 
{                                                                                                                           
    auto rq = Request();                                                                                                    
    rq.useStreaming = true;                                                                                                 
    auto stream = rq.get("http://httpbin.org/get").receiveAsRange();                                                        
    auto content = postContent("http://httpbin.org/post", stream);                                                          
    writeln(content);                                                                                                       
}                                                                                                                           

You can use dlang-requests in parallel tasks (but you can't share the same Request structure between threads):

import std.stdio;
import std.parallelism;
import std.algorithm;
import std.string;
import core.atomic;
import requests;

immutable auto urls = [
    "http://httpbin.org/stream/10",
    "http://httpbin.org/stream/20",
    "http://httpbin.org/stream/30",
    "http://httpbin.org/stream/40",
    "http://httpbin.org/stream/50",
    "http://httpbin.org/stream/60",
    "http://httpbin.org/stream/70",
];

void main() {
    defaultPoolThreads(5);

    shared short lines;

    foreach(url; parallel(urls)) {
        atomicOp!"+="(lines, getContent(url).splitter("\n").count);
    }
    assert(lines == 287);
}
File download example

Note: use "wb" and rawWrite with file.

import requests;
import std.stdio;

void main() {
    Request rq = Request();
    Response rs = rq.get("http://geoserver.readthedocs.io/en/latest/_images/imagemosaiccreate1.png");
    File f = File("123.png", "wb"); // do not forget to use both "w" and "b" modes when open file.
    f.rawWrite(rs.responseBody.data);
    f.close();
}

Loading whole document to memory and then save it might be impractical or impossible. Use streams in this case:

import requests;
import std.stdio;

void main() {
    Request rq = Request();

    rq.useStreaming = true;
    auto rs = rq.get("http://geoserver.readthedocs.io/en/latest/_images/imagemosaiccreate1.png");
    auto stream = rs.receiveAsRange();
    File file = File("123.png", "wb");

    while(!stream.empty)  {
        file.rawWrite(stream.front);
        stream.popFront;
    }
    file.close();
}

vibe.d

You can safely use dlang-requests with vibe.d. When dlang-requests is compiled with support for vibe.d sockets (--config=vibed), each call to dlang-requests API can block only the current fiber, not the thread:

import requests, vibe.d;

shared static this()
{
    void taskMain()
    {
        logInfo("Task created");
        auto r1 = getContent("http://httpbin.org/delay/3");
        logInfo("Delay request finished");
        auto r2 = getContent("http://google.com");
        logInfo("Google request finished");
    }

    setLogFormat(FileLogger.Format.threadTime, FileLogger.Format.threadTime);
    for(size_t i = 0; i < 3; i++)
        runTask(&taskMain);
}

Output:

[F7EC2FAB:F7ECD7AB 2016.07.05 16:55:54.115 INF] Task created
[F7EC2FAB:F7ECD3AB 2016.07.05 16:55:54.116 INF] Task created
[F7EC2FAB:F7ED6FAB 2016.07.05 16:55:54.116 INF] Task created
[F7EC2FAB:F7ECD7AB 2016.07.05 16:55:57.451 INF] Delay request finished
[F7EC2FAB:F7ECD3AB 2016.07.05 16:55:57.464 INF] Delay request finished
[F7EC2FAB:F7ED6FAB 2016.07.05 16:55:57.474 INF] Delay request finished
[F7EC2FAB:F7ECD7AB 2016.07.05 16:55:57.827 INF] Google request finished
[F7EC2FAB:F7ECD3AB 2016.07.05 16:55:57.836 INF] Google request finished
[F7EC2FAB:F7ED6FAB 2016.07.05 16:55:57.856 INF] Google request finished

Adding/replacing request headers

Use string[string] and addHeaders() method to add or replace some request headers.

User-supplied headers override headers, created by library code, so you have to be careful adding common headers, like Content-Type, Content-Length, etc..

import requests;

void main() {
    auto rq = Request();
    rq.verbosity = 2;
    rq.addHeaders(["User-Agent": "test-123", "X-Header": "x-value"]);
    auto rs = rq.post("http://httpbin.org/post", `{"a":"b"}`, "application/x-www-form-urlencoded");
}

Output:

> POST /post HTTP/1.1
> Content-Length: 9
> Connection: Keep-Alive
> User-Agent: test-123
> Accept-Encoding: gzip, deflate
> Host: httpbin.org
> X-Header: x-value
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 200 OK
< server: nginx
...

SSL settings

HTTP requests can be configured for SSL options: you can enable or disable remote server certificate verification, set key and certificate to use for authorizing to remote server:

  • sslSetVerifyPeer(bool) - turn ssl peer verification on or off (on by default since v0.8.0)
  • sslSetKeyFile(string) - load client key from file
  • sslSetCertFile(string) - load client cert from file
  • sslSetCaCert(string) - load server CA cert for private or self-signed server certificates
import std.stdio;
import requests;
import std.experimental.logger;

void main() {
    globalLogLevel(LogLevel.trace);
    auto rq = Request();
    rq.sslSetKeyFile("client01.key"); // set key file
    rq.sslSetCertFile("client01.crt"); // set cert file
    auto rs = rq.get("https://dlang.org/");
    writeln(rs.code);
    writeln(rs.responseBody);
}

Please note that with vibe.d you have to add the following call

rq.sslSetCaCert("/opt/local/etc/openssl/cert.pem");

with path to CA cert file (location may differ for different OS or openssl packaging).

By default ssl peer verification turned ON. This can lead to problems in case you use server-side self-signed certificates. To fix, you have either add server ca.crt to trusted store on local side(see https://unix.stackexchange.com/questions/90450/adding-a-self-signed-certificate-to-the-trusted-list for example), or use sslSetCaCert to add it for single requests call(rq.sslSetCaCert("ca.crt");), or just disable peer verification with rq.sslSetVerifyPeer(false);

FTP requests

You can use the same structure to make ftp requests, both get and post.

HTTP-specific methods do not work if request uses ftp scheme.

Here is an example:

import std.stdio;
import requests;

void main() {
    auto rq = Request();
    rq.verbosity = 3;
    rq.authenticator = new BasicAuthentication("login", "password");
    auto f = File("test.txt", "rb");
    auto rs = rq.post("ftp://example.com/test.txt", f.byChunk(1024));
    writeln(rs.code);
    rs = rq.get("ftp://@example.com/test.txt");
    writeln(rs.code);
}

Second argument for FTP posts can be anything that can be casted to ubyte[] or any InputRange with element type like ubyte[]. If the path in the post request doesn't exist, it will try to create all the required directories. As with HTTP, you can call several FTP requests using the same Request structure - it will reuse established connection (and authorization as well).

FTP LIST command

To retrieve single file properties or directory listing use rq.execute method:

import std.stdio;
import requests;

void main()
{
    auto rq = Request();
    auto rs = rq.execute("LIST", "ftp://ftp.iij.ad.jp/pub/FreeBSD/");
    writeln(rs.responseBody);

}

output:

-rw-rw-r--    1 ftp      ftp            35 May 12 09:00 TIMESTAMP
drwxrwxr-x    9 ftp      ftp           169 Oct 05  2015 development
-rw-r--r--    1 ftp      ftp          2871 May 11 10:00 dir.sizes
drwxrwxr-x   22 ftp      ftp          8192 May 09 23:00 doc
drwxrwxr-x    6 ftp      ftp            92 Jan 10 21:38 ports
drwxrwxr-x   12 ftp      ftp           237 Feb 06  2021 releases
drwxrwxr-x   12 ftp      ftp           237 May 05 18:00 snapshots

Interceptors

Interceptors provide a way to modify, or log, or cache request. They can form a chain attached to Request structure so that each request will pass through whole chain.

Each interceptor receive request as input, do whatever it need and pass request to the handler, which finally serve request and return Response back.

Here is small example how interceptors can be used. Consider situation where you have main app and some module. Main code:

import std.stdio;
import mymodule;

void main()
{
    auto r = mymodule.doSomething();
    writeln(r.length);
}

module:

module mymodule;

import requests;

auto doSomething()
{
    return getContent("http://google.com");                                                                              
}

One day you decide that you need to log every http request to external services.

One solution is to add logging code to each function of mymodule where external http calls executed. This can require lot of work and code changes, and sometimes even not really possible.

Another, and more effective solution is to use interceptors. First we have to create logger class:

class LoggerInterceptor : Interceptor {
    Response opCall(Request r, RequestHandler next)
    {
        writefln("Request  %s", r);
        auto rs = next.handle(r);
        writefln("Response %s", rs);
        return rs;
    }
}

Then we can instrument every call to request with this call:

import std.stdio;
import requests;
import mymodule;

class LoggerInterceptor : Interceptor {
    Response opCall(Request r, RequestHandler next)
    {
        writefln("Request  %s", r);
        auto rs = next.handle(r);
        writefln("Response %s", rs);
        return rs;
    }
}

void main()
{
    requests.addInterceptor(new LoggerInterceptor());
    auto r = mymodule.doSomething();
    writeln(r.length);
}

The only change required is call addInterceptor().

You may intercept single Request structure (instead of whole requests module) attaching interceptors directly to this structure:

Request rq;
rq.addInterceptor(new LoggerInterceptor());

Interceptor can change Request r, using Request() getters/setters before pass it to next handler. For example, authentication methods can be added using interceptors and headers injection. You can implement some kind of cache and return cached response immediately.

To change POST data in the interceptors you can use makeAdapter (since v1.1.2). Example:

import std.stdio;
import std.string;
import std.algorithm;
import requests;

class I : Interceptor
{
    Response opCall(Request rq, RequestHandler next)
    {
        rq.postData = makeAdapter(rq.postData.map!"toUpper(cast(string)a)");
        auto rs = next.handle(rq);
        return rs;
    }
}

void main()
{
    auto f = File("text.txt", "rb");
    Request rq;
    rq.verbosity = 1;
    rq.addInterceptor(new I);
    auto rs = rq.post("https://httpbin.org/post", f.byChunk(5));
    writeln(rs.responseBody);
}

SocketFactory

If configured - each time when Request need new connection it will call factory to create instance of NetworkStream. This way you can implement (outside of this library) lot of useful things: various proxies, unix-socket connections, etc.

Response structure

This structure provides details about received response.

Most frequently needed parts of Response are:

  • code - HTTP or FTP response code as received from server.
  • responseBody - contain complete document body when no streaming is in use. You can't use it when in streaming mode.
  • responseHeaders - response headers in form of string[string] (not available for FTP requests)
  • receiveAsRange - if you set useStreaming in the Request, then receiveAsRange will provide elements (type ubyte[]) of InputRange while receiving data from the server.

Requests Pool

When you have a large number of requests to execute, you can use a request pool to speed things up.

A pool is a fixed set of worker threads, which receives requests in form of Jobs and returns Results.

Each Job can be configured for an URL, method, data (for POST requests) and some other parameters.

pool acts as a parallel map from Job to Result - it consumes InputRange of Jobs, and produces InputRange of Results as fast as it can.

It is important to note that pool does not preserve result order. If you need to tie jobs and results somehow, you can use the opaque field of Job.

Here is an example usage:

import std.algorithm;
import std.datetime;
import std.string;
import std.range;
import requests;

void main() {
    Job[] jobs = [
        Job("http://httpbin.org/get").addHeaders([
                            "X-Header": "X-Value",
                            "Y-Header": "Y-Value"
                        ]),
        Job("http://httpbin.org/gzip"),
        Job("http://httpbin.org/deflate"),
        Job("http://httpbin.org/absolute-redirect/3")
                .maxRedirects(2),
        Job("http://httpbin.org/range/1024"),
        Job("http://httpbin.org/post")                                                                               
                .method("POST")                     // change default GET to POST
                .data("test".representation())      // attach data for POST
                .opaque("id".representation),       // opaque data - you will receive the same in Result
        Job("http://httpbin.org/delay/3")
                .timeout(1.seconds),                // set timeout to 1.seconds - this request will throw exception and fails
        Job("http://httpbin.org/stream/1024"),
    ];

    auto count = jobs.
        pool(6).
        filter!(r => r.code==200).
        count();

    assert(count == jobs.length - 2, "pool test failed");
    iota(20)
        .map!(n => Job("http://httpbin.org/post")
                        .data("%d".format(n).representation))
        .pool(10)
        .each!(r => assert(r.code==200));
}

One more example, with more features combined:

import requests;
import std.stdio;
import std.string;

void main() {
    Job[] jobs_array = [
        Job("http://0.0.0.0:9998/3"),
        Job("http://httpbin.org/post").method("POST").data("test".representation()).addHeaders(["a":"b"]),
        Job("http://httpbin.org/post", Job.Method.POST, "test".representation()).opaque([1,2,3]),
        Job("http://httpbin.org/absolute-redirect/4").maxRedirects(2),
    ];
    auto p = pool(jobs_array, 10);
    while(!p.empty) {
        auto r = p.front;
        p.popFront;
        switch(r.flags) {
        case Result.OK:
            writeln(r.code);
            writeln(cast(string)r.data);
            writeln(r.opaque);
            break;
        case Result.EXCEPTION:
            writefln("Exception: %s", cast(string)r.data);
            break;
        default:
            continue;
        }
        writeln("---");
    }
}

Output:

2016-12-29T10:22:00.861:streams.d:connect:973 Failed to connect to 0.0.0.0:9998(0.0.0.0:9998): Unable to connect socket: Connection refused
2016-12-29T10:22:00.861:streams.d:connect:973 Failed to connect to 0.0.0.0:9998(0.0.0.0:9998): Unable to connect socket: Connection refused
Exception: Can't connect to 0.0.0.0:9998
---
200
{
  "args": {},
  "data": "test",
  "files": {},
  "form": {},
  "headers": {
    "A": "b",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "4",
    "Content-Type": "application/octet-stream",
    "Host": "httpbin.org",
    "User-Agent": "dlang-requests"
  },
  "json": null,
  "origin": "xxx.xxx.xxx.xxx",
  "url": "http://httpbin.org/post"
}

[]
---
200
{
  "args": {},
  "data": "test",
  "files": {},
  "form": {},
  "headers": {
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "4",
    "Content-Type": "application/octet-stream",
    "Host": "httpbin.org",
    "User-Agent": "dlang-requests"
  },
  "json": null,
  "origin": "xxx.xxx.xxx.xxx",
  "url": "http://httpbin.org/post"
}

[1, 2, 3]
---
Exception: 2 redirects reached maxRedirects 2.
---

Job methods

name parameter type description
method string "GET" or "POST" request method
data immutable(ubyte)[] data for POST request
timeout Duration timeout for network IO
maxRedirects uint max. no. of redirects
opaque immutable(ubyte)[] opaque data
addHeaders string[string] headers to add to request

Result fields

name type description
flags uint flags (OK,EXCEPTION)
code ushort response code
data immutable(ubyte)[] response body
opaque immutable(ubyte)[] opaque data from job

Pool limitations

  1. Currently it doesn't work under vibe.d - use vibe.d parallelisation.
  2. It limits you in tuning request (e.g. you can add authorization only through addHeaders(), you can't tune SSL parameters, etc).
  3. Job's' and Result's' data are immutable byte arrays (as it uses send/receive for data exchange).

International Domain names

dlang-requests supports IDNA through idna package. It provide correct conversion between unicode domain names and punycode, but have limited ability to check names for standard compliance.

Static Build

By default dlang-requests links to the openssl and crypto libraries dynamically. There is a staticssl dub configuration (linux only!) in order to link statically to these libraries.

This is often used to generate a "distro-less" binaries, typically done on alpine using musl's libc.

dlang-requests's People

Contributors

9il avatar andresclari avatar burner avatar cschlote avatar geod24 avatar gizmomogwai avatar goalitium avatar iklfst avatar ikod avatar japplegame avatar jpf91 avatar nexor avatar sarneaud avatar schveiguy avatar skoppe avatar webfreak001 avatar ximion avatar zhad3 avatar zorael avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dlang-requests's Issues

Getting "Program exited with code -1073741515" when importing requests

Here's my d file:

import requests;
import std.stdio;

void main()
{
	writeln("Test");
}

and here's my dub file:

{
    "name": "dlang-requests-test",
    "authors": [
        "Test"
    ],
    "description": "A minimal D application.",
    "copyright": "Copyright © 2017, Test",
    "license": "proprietary",
    "dependencies": {
        "requests": "~>0.4.2"
    }
}

When running dub it compiles and links but when running the executable it outputs "Program exited with code -1073741515" and main never gets called.

Using DMD32 v2.074.1 and Dub v1.3.0 on Windows 7. Any help would be appreciated.

better FTP file coping

  1. void [] and std.file.read improving (need cast(ubyte) or we will get FTP copying error)
  2. ability to specify name of file for uploading without explicitly read(file_name) for copying

Ftp requests fail if user name contains @ sign

import std.stdio;
import requests;

void main() {
    auto rq = Request();
    rq.verbosity = 3;
    auto f = File("test.txt", "rb");
    auto rs = rq.post("ftp://login:[email protected]/test.txt", f.byChunk(1024));
    writeln(rs.code);
    rs = rq.get("ftp://login:[email protected]/test.txt");
    writeln(rs.code);
}

Consider this example. If login name is like that [email protected] ftp requests fail with this exception.

std.conv.ConvException@/usr/include/dmd/phobos/std/conv.d(2083): Unexpected 'a' when converting from type string to type uint

??:? pure @safe uint std.conv.parse!(uint, immutable(char)[]).parse(ref immutable(char)[]) [0x81b4fce]
??:? pure @safe ushort std.conv.parse!(ushort, immutable(char)[]).parse(ref immutable(char)[]) [0x81d4880]
??:? pure @safe ushort std.conv.toImpl!(ushort, immutable(char)[]).toImpl(immutable(char)[]) [0x81d48fc]
??:? pure @safe ushort std.conv.to!(ushort).to!(immutable(char)[]).to(immutable(char)[]) [0x81d4865]
??:? pure @safe bool requests.uri.URI.uri_parse(immutable(char)[]) [0x81d9152]
??:? pure ref @safe requests.uri.URI requests.uri.URI.__ctor(immutable(char)[]) [0x81d8ea3]
??:? requests.base.Response requests.Request.post!(std.stdio.File.ByChunk).post(immutable(char)[], std.stdio.File.ByChunk) [0x818d340]
??:? void upload.uploadToServer() [0x8196bcb]
??:? _Dmain [0x818b763]
??:? _D2rt6dmain211_d_run_mainUiPPaPUAAaZiZ6runAllMFZ9__lambda1MFZv [0x8210322]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])).tryExec(scope void delegate()) [0x821026c]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])
).runAll() [0x82102de]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x821026c]
??:? _d_run_main [0x82101fe]
??:? main [0x818f073]
??:? __libc_start_main [0x6fd532]

Also I think specifying login and password in url is not a good choice.

Erdem

Sequenced .get() with useStreaming doesn't clean previous unfinished data

Try this:

import requests;

int main(string[] args) {
  auto rq = Request();
  // rq.verbosity = 3;
  rq.addHeaders(["Accept-Encoding": "deflate"]);
  rq.useStreaming = true;

  rq.get("http://www.wilko.com/content/ebiz/wilkinsonplus/invt/3112340/3112340_l.jpg");
  rq.get("http://www.wilko.com/content/ebiz/wilkinsonplus/invt/3112340/3112340_l.jpg");
  return 0;

}

the data from previous stream will be used as HTTP answer for the second one, producing exception.

Request.addHeaders doesn't control for duplicated headers

        auto rq = Request();
        rq.addHeaders(["User-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"]);

produce following request:

> GET / HTTP/1.1
> Connection: Keep-Alive
> User-Agent: dlang-requests
> Accept-Encoding: gzip, deflate
> Host: www.intel.com
> User-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36

Added header should replace previous default one.

replace std.net.curl

I am pretty sure you are aware of this thread.

Many people want to get rid off the external dependency to curl, however

We need a replacement before we deprecate curl.

So let's join forces to create a great replacement?

@ikod if you are interested to go this road, you would need to relicense your library under the Boost license.

Ping at the usual people: @burner @JackStouffer @ZombineDev @schveiguy @CyberShadow @DmitryOlshansky @GilesBathgate - have you given this lib already a try?
A good way to help out is probably bug all design flaws and stuff you don't like as issues ;-)

Additional error message printed even after exception is caught.

I see you've already fixed this but you wanted me to put the report on here so here you got :)

If a url can't be found and I catch the ConnectError exception and handle it[2] I still get an additional error[1] printed. For example:

        try
        {
            getContent(url);
        }
        catch(ConnectError ex)
        {
            debug writeln("Error getting page: ", ex.msg);
        }

Will error with this:

[1]2016-06-21T20:59:15.073:streams.d:connect:750 Failed to connect: can't resolve www.systemcontrolpanel.com - getaddrinfo error: Name or service not known

[2]Error getting page: Can't connect to www.systemcontrolpanel.com:80: getaddrinfo error: Name or service not known

The additional error[1] will still print in release build. The only workaround I could find was to check getAddress myself and ensure that the page exists before calling getContent.

Link error with new openssl 1.1.0e

Arch linux recently updated their openssl package to 1.1.0e, and with it dlang-requests no longer links.

$ dub build
Performing "debug" build using dmd for x86_64.
requests 0.4.0: target for configuration "std" is up to date.
kameloso ~master: building configuration "kameloso"...
source/kameloso/config.d(66,13): Deprecation: 'switch' skips declaration of variable kameloso.config.walkConfigLines!(Result, IrcBot, IrcServer).walkConfigLines.thing at source/kameloso/config.d(68,13)
Linking...
../../.dub/packages/requests-0.4.0/requests/.dub/build/std-debug-linux.posix-x86_64-dmd_2074-E4CF0CB26A60CA71D7E3266518AEBC37/librequests.a(streams.o): In function `_D8requests7streams19_sharedStaticCtor46FZv':
/home/zorael/src/kameloso/../../.dub/packages/requests-0.4.0/requests/source/requests/streams.d:778: undefined reference to `SSL_library_init'
/home/zorael/src/kameloso/../../.dub/packages/requests-0.4.0/requests/source/requests/streams.d:779: undefined reference to `OpenSSL_add_all_ciphers'
/home/zorael/src/kameloso/../../.dub/packages/requests-0.4.0/requests/source/requests/streams.d:780: undefined reference to `OpenSSL_add_all_digests'
/home/zorael/src/kameloso/../../.dub/packages/requests-0.4.0/requests/source/requests/streams.d:781: undefined reference to `SSL_load_error_strings'
collect2: error: ld returned 1 exit status
Error: linker exited with status 1
dmd failed with exit code 1.

There now exists an openssl-1.0 package with the old library (.so-versioned), but installing it doesn't seem to be enough.

rewrite Buffer to reduce memory allocations

Buffer is wrapper for array of ubyte[] - it can be collection of raw data chunks received from network, results of gzipped content decompressed, or "chunked" content "unchunked". Data received from network can be shared and referenced from different parts of code (you can continue to receive content-chunked at the same time trying to "unchunk" it).

Buffer propose Range interfaces, so it can be easily processed with std.algorithms. But, to be safe and fast, Buffer must use immutable underlying arrays.

After switching from current implementation in master branch (which use mutable data and have to perform copy when Buffer have to be shared) to immutable(ubyte)[] in 1.0.0-dev branch, GC metrics become much better:

-------------------------------------------------------------------------
* master
    Number of collections:  2234
    Total GC prep time:  26 milliseconds
    Total mark time:  338 milliseconds
    Total sweep time:  361 milliseconds
    Total page recovery time:  139 milliseconds
    Max Pause Time:  0 milliseconds
    Grand total GC time:  865 milliseconds
GC summary:    5 MB, 2234 GC  865 ms, Pauses  364 ms <    0 ms
time ./perftest
       16.49 real         8.63 user         0.51 sys
-------------------------------------------------------------------------
* 1.0.0-dev
    Number of collections:  539
    Total GC prep time:  6 milliseconds
    Total mark time:  74 milliseconds
    Total sweep time:  105 milliseconds
    Total page recovery time:  37 milliseconds
    Max Pause Time:  0 milliseconds
    Grand total GC time:  223 milliseconds
GC summary:    5 MB,  539 GC  223 ms, Pauses   80 ms <    0 ms
time ./perftest
       15.93 real         5.11 user         0.42 sys

for the next simple code:

import std.stdio;
import requests;
import std.algorithm;


void main() {
    auto rq = Request();
    for(int i=0;i<12345;i++) {
        auto r = rq.get("http://127.0.0.1:8081/get");
        if ( r.code != 200 ) {
            writeln("bug");
        }
        auto c = r.responseBody.splitter('\n').count;
        if ( c != 289 ) {
            writeln("bug");
        }
    }
}

Support http_proxy and https_proxy

Would it be possible to support environment variables for proxies? (http_proxy, https_proxy, ALL_PROXY). These variables are commonly used on unix-like systems to enable proxies.

Segfault of unknown origin

I am writing a command line tool which scrapes a REST API. The following code, with vibed support turned on, segfaults during shutdown of the application. If the Request struct is moved into the Projects function scope. My presumption is that RestClient ends up finalized after some of the vibed internal objects, and during the RestClient finalization the rq struct is destroyed as well. This may be accessing some vibed internals that are already gone?

 class RestClient {
 	Request rq;

 	this(string user, string passwd) {
 		rq.verbosity = 0;
 		rq.authenticator = new BasicAuthentication(user, passwd);
 	}

 	auto Projects() {
 		auto rs = rq.get("https://xxxxx/rest/api/1.0/projects");
 		//auto pr = parseJsonString(rs.responseBody.to!string);

 		return "Hello";
 	}
}

dlang-requests requere OpenSSL on http url

import std.stdio;
import requests;
import std.file;

void main()
{
	Request rq = Request();
	Response rs = rq.get("http://geoserver.readthedocs.io/en/latest/_images/imagemosaiccreate1.png");
	File f = File("123.png", "w");
	f.write(rs.responseBody.data);

}

[email protected]\requests\source\requests\ssl_adapter.d(59): loading libssl: error 0 Program exited with code 1

Build fails using dmd 2.072.0

dlang-requests fails to build using dmd/libphobos 2.072.0 and dub 1.0.0 on Arch Linux, x86_64.

I tried a clean build from the git sources as well as a build from a project depending on requests.

The error message is:

source/requests/server/httpd.d(714,9): Error: pure constructor 'requests.server.httpd.RequestArgs.this' cannot call impure destructor 'std.regex.Captures!(string, ulong).Captures.~this'

отличия от vibed

Не мог бы рассказать, чем твой модуль от аналогичной реализации в vibed отличается. На сколько их стоит в одном проекте смешивать?

Возможно этим бы даже readme стоило дополнить.

It gives a lot of linker errors

When I try to compile simple example it gives a lot of linker errors.

dmd -I/home//dlang/import -L-L/home//dlang/lib -L-l:libhtmld.a -L-l:librequests.a test.d -oftest
/home//dlang/lib/librequests.a(streams.o): In function _D8requests7streams19_sharedStaticCtor45FZv': streams.d:778: undefined reference to SSL_library_init'
streams.d:779: undefined reference to OpenSSL_add_all_ciphers' streams.d:780: undefined reference to OpenSSL_add_all_digests'
streams.d:781: undefined reference to SSL_load_error_strings' /home//dlang/lib/librequests.a(streams_3be_52d.o): In function _D8requests7streams13OpenSslSocket7initSslMFS8requests7streams10SSLOptionsZv':
streams.d:795: undefined reference to TLSv1_client_method' streams.d:795: undefined reference to SSL_CTX_new'
streams.d:798: undefined reference to SSL_CTX_set_default_verify_paths' streams.d:800: undefined reference to SSL_CTX_load_verify_locations'
streams.d:802: undefined reference to SSL_CTX_set_verify' streams.d:810: undefined reference to SSL_CTX_use_PrivateKey_file'
streams.d:811: undefined reference to SSL_CTX_use_certificate_file' streams.d:814: undefined reference to SSL_CTX_use_PrivateKey_file'
streams.d:815: undefined reference to SSL_CTX_use_certificate_file' streams.d:818: undefined reference to SSL_CTX_use_PrivateKey_file'
streams.d:819: undefined reference to SSL_CTX_use_certificate_file' streams.d:826: undefined reference to SSL_new'
streams.d:826: undefined reference to SSL_set_fd' /home//dlang/lib/librequests.a(streams_3be_52d.o): In function _D8requests7streams13OpenSslSocket7connectMFNeC3std6socket7AddressZv':
streams.d:833: undefined reference to SSL_connect' streams.d:834: undefined reference to ERR_get_error'
streams.d:834: undefined reference to ERR_reason_error_string' /home//dlang/lib/librequests.a(streams_3be_52d.o): In function _D8requests7streams13OpenSslSocket4sendMFNeAxvE3std6socket11SocketFlagsZi':
streams.d:840: undefined reference to SSL_write' /home//dlang/lib/librequests.a(streams_3be_52d.o): In function _D8requests7streams13OpenSslSocket7receiveMFNeAvE3std6socket11SocketFlagsZi':
streams.d:847: undefined reference to SSL_read' /home//dlang/lib/librequests.a(streams_3be_52d.o): In function _D8requests7streams13OpenSslSocket6__dtorMFZv':
streams.d:866: undefined reference to SSL_free' streams.d:866: undefined reference to SSL_CTX_free'
collect2: error: ld returned 1 exit status
Error: linker exited with status 1
make: *** [test] Error 1

Compilation exited abnormally with code 2 at Sat Dec 3 23:24:20

release new (working) version

the current dub version doesn't compile due to the missing package.d - do you to release a new version so other people don't run into this issue?

working smoothly with serialization

@JackStouffer correctly pointed out that Python's requests library should be a inspiration point (e.g. read the user testimonials for the reason).

One important feature is automatic serialization which is quite handy, so maybe we can keep this in mind that plugin a serializer / deserializer is an important use case.

Add windows ssl functionality in addition to openssl

Please add the option to use windows ssl functionality in addition to openssl. There are two major benefits:

  • No need to copy / package openssl libraries
  • In case there are implementation issues with the windows ssl library, these issue will be solved automatically by Microsoft with the next update. In case of openssl, the corrected library has to be shipped to all customers using the application

Making requests in parallel

Hey I tried to make a couple of requests in parallel, but it seems that it isn't that straight forward?

version(unittest)
auto myRequest(string x)
{
    import requests;
    alias get = getContent;
    import std.stdio;
    writeln("request", x);
    return get("https://en.wikipedia.org/wiki/Test");
};

unittest
{
    import std.parallelism;
    import std.range: iota;
    import std.algorithm: map;
    import std.conv: to;
    auto r = iota(0, 100).map!(to!string);
    auto p = taskPool.map!myRequest(r); // fails
    import std.stdio;
    writeln("len", p.length);
}

I would guess that's a problem with the buffer being reused?
That's the error message I get:

std.exception.ErrnoException@(0): receiving Headers (Interrupted system call)
----------------
../../../.dub/packages/requests-0.1.3/requests/source/requests/http.d:528 void requests.http.Request.receiveResponse() [0x89d7c2]
../../../.dub/packages/requests-0.1.3/requests/source/requests/http.d:958 requests.http.Response requests.http.Request.exec!("GET").exec(immutable(char)[], immutable(char)[][immutable(char)[]]) [0x882362]
../../../.dub/packages/requests-0.1.3/requests/source/requests/http.d:94 requests.streams.Buffer!(ubyte).Buffer requests.http.getContent!().getContent(immutable(char)[]) [0x881f53]
source/app.d:62 requests.streams.Buffer!(ubyte).Buffer contribs.app.myRequest(immutable(char)[]) [0x843203]
/usr/include/dlang/dmd/std/parallelism.d:1733 _D3std11parallelism8TaskPool82__T4amapS70_D8contribs3app9myRequestFAyaZS8requests7streams13__T6BufferThZ6BufferZ176__T4amapTS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultTmTAS8requests7streams13__T6BufferThZ6BufferZ4amapMFS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultmAS8requests7streams13__T6BufferThZ6BufferZ4doItMFZv [0x888f20]
??:? void std.parallelism.run!(void delegate()).run(void delegate()) [0xa765a3]
??:? void std.parallelism.__T4TaskS213std11parallelism3runTDFZvZ.Task.impl(void*) [0xa760e3]
??:? void std.parallelism.AbstractTask.job() [0xa74c9a]
??:? void std.parallelism.submitAndExecute(std.parallelism.TaskPool, scope void delegate()) [0xa75dc6]
/usr/include/dlang/dmd/std/parallelism.d:1747 requests.streams.Buffer!(ubyte).Buffer[] std.parallelism.TaskPool.amap!(requests.streams.Buffer!(ubyte).Buffer contribs.app.myRequest(immutable(char)[])).amap!(std.algorithm.iteration.__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ.MapResult, ulong, requests.streams.Buffer!(ubyte).Buffer[]).amap(std.algorithm.iteration.__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ.MapResult, ulong, requests.streams.Buffer!(ubyte).Buffer[]) [0x888da0]
/usr/include/dlang/dmd/std/parallelism.d:2001 _D3std11parallelism8TaskPool81__T3mapS70_D8contribs3app9myRequestFAyaZS8requests7streams13__T6BufferThZ6BufferZ131__T3mapTS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultZ3mapMFS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultmmZ3Map7fillBufMFAS8requests7streams13__T6BufferThZ6BufferZAS8requests7streams13__T6BufferThZ6Buffer [0x887d01]
/usr/include/dlang/dmd/std/parallelism.d:1952 _D3std11parallelism8TaskPool81__T3mapS70_D8contribs3app9myRequestFAyaZS8requests7streams13__T6BufferThZ6BufferZ131__T3mapTS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultZ3mapMFS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultmmZ3Map6__ctorMFS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultmmC3std11parallelism8TaskPoolZC3std11parallelism8TaskPool81__T3mapS70_D8contribs3app9myRequestFAyaZS8requests7streams13__T6BufferThZ6BufferZ131__T3mapTS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultZ3mapMFS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultmmZ3Map [0x887b32]
/usr/include/dlang/dmd/std/parallelism.d:1828 _D3std11parallelism8TaskPool81__T3mapS70_D8contribs3app9myRequestFAyaZS8requests7streams13__T6BufferThZ6BufferZ131__T3mapTS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultZ3mapMFS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultmmZC3std11parallelism8TaskPool81__T3mapS70_D8contribs3app9myRequestFAyaZS8requests7streams13__T6BufferThZ6BufferZ131__T3mapTS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultZ3mapMFS3std9algorithm9iteration85__T9MapResultS253std4conv11__T2toTAyaZ2toTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ9MapResultmmZ3Map [0x8879a5]
source/app.d:74 void contribs.app.__unittestL65_2() [0x84326b]
??:? void contribs.app.__modtest() [0x895e50]
??:? int core.runtime.runModuleUnitTests().__foreachbody2(object.ModuleInfo*) [0xa9b4dc]
??:? int object.ModuleInfo.opApply(scope int delegate(object.ModuleInfo*)).__lambda2(immutable(object.ModuleInfo*)) [0xa43213]
??:? int rt.minfo.moduleinfos_apply(scope int delegate(immutable(object.ModuleInfo*))).__foreachbody2(ref rt.sections_elf_shared.DSO) [0xa50106]
??:? int rt.sections_elf_shared.DSO.opApply(scope int delegate(ref rt.sections_elf_shared.DSO)) [0xa5064d]
??:? int rt.minfo.moduleinfos_apply(scope int delegate(immutable(object.ModuleInfo*))) [0xa50097]
??:? int object.ModuleInfo.opApply(scope int delegate(object.ModuleInfo*)) [0xa431ef]
??:? runModuleUnitTests [0xa9b3ce]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).runAll() [0xa4b286]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0xa4b228]
??:? _d_run_main [0xa4b199]
??:? main [0x89ab2f]
??:? __libc_start_main [0x890c3740]

adding custom headers to a post

Is there a way to add specific headers to Request() like in python?

headers = dict()
headers["Content-Type"] = "application/json"
headers["Accept"]            = "application/json"
headers[“Auth-Token”]   = "FB355GG213234"

r = requests.post("https://some-rest.service.com/restapi", headers=headers, json=some_json)

Send data to webpage

Hello,

i am using requests on windows and i am having problems to send data to a webpage. My current approach is this:

Request rq = Request();
Response rs = rq.exec!"GET"("http://somewebpage.org/", [parameter:data]);

Even though i get the response code '200' my website i am sending the data to does not react to the data (if it receives any, i don't know). If i just type this in my browser:

http://somewebpage.org/?parameter=data

everything works as intended.

What am i doing wrong?

Не получается отправить объект JSON

void sendQuestionsToArangoDB(Json questions)
{
    string collectionUrl = "http://localhost:8529/_db/onlinetest/_api/document/?collection=sitetestanswers";
    auto rq = Request();
    rq.verbosity = 2;
    string s = `{"test":"some value111"}`;
    auto rs = rq.post(collectionUrl, s, "application/json");
    writeln(s);
    writeln("SENDED");


}

Стоит вместо: s отправить questions. Во первых не компилится:
C:\Users\bubenkov_di\AppData\Roaming\dub\packages\requests-0.1.0\requests\source\requests\http.d(1212,27): Error: template requests.http.Request.exec cannot deduce function from argument types !("POST")(string, Json, string), candidates are:

Во вторых если в to!string сделать, то вылетает ошибка:

> POST /_db/onlinetest/_api/document/?collection=sitetestanswers HTTP/1.1
> Content-Length: 2941
> Connection: Close
> Host: localhost:8529
> Content-Type: application/json
>
< HTTP/1.1 400 Bad Request
< Server: ArangoDB
< Connection: Close
< Content-Type: application/json; charset=utf-8
< Content-Length: 80
< 80 bytes of body received
SENDED

Не могу скомпилировать проект

Вот так выглядит проект, который хочу собрать:

import std.stdio;
import stdx.data.json;
import requests.http;

void main() 
{
    string url = "http://localhost:8529/_db/otest/_api/document?collection=visitors";

        struct S
        {
            string [] myarr;
        }

        S s;

        auto rq = Request();
        auto rs = rq.get("https://httpbin.org/");
        //auto rs2 = rq.get(url);

}

Однако при компиляции сыпет ошибками:

 Warning 2: File Not Found crypto.lib
ssl.lib
 Warning 2: File Not Found ssl.lib
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _OpenSSL_add_all_digests
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _SSL_library_init
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _OpenSSL_add_all_ciphers
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _SSL_load_error_strings
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _SSL_new
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _SSL_CTX_new
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _TLSv1_client_method
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _SSL_set_fd
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _SSL_connect
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _SSL_write
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _SSL_read
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _SSL_free
C:\Users\Dima\AppData\Roaming\dub\packages\requests-0.1.0\requests\requests.lib(http)
 Error 42: Symbol Undefined _SSL_CTX_free
--- errorlevel 13

Segfault fetching URL, ERR_get_error() returns ERR_get_error()

import requests;

void main()
{
    Req req;
    req.get("https://www.humblebundle.com/store");  // segfaults
}

GDB stacktrace is thousands of lines of requests.ssl_adapter.OpenSSL.ERR_get_error() const (this=...) at ../../../.dub/packages/requests-0.5.1/requests/source/requests/ssl_adapter.d:232;

ulong ERR_get_error() const {
    return ERR_get_error();
}

vibe-core is now a package

This is just an FYI that since a couple of weeks vibe-core is it's own package.
While Vibe.d is still in the process of switching to it.

https://github.com/vibe-d/vibe-core

The new core module contains only the basic functionality (e.g. connectTCP), but not any SSL functionality like createTLSContext

https://github.com/vibe-d/vibe-core/blob/master/source/vibe/core/net.d
https://github.com/vibe-d/vibe-core/blob/master/source/vibe/core/stream.d

I am not sure how Vibe.d will move forward, probably there will be sth. like a vibe-ssl package, so this was intended just as a FYI for you to keep an eye on it ;-)

edit: I opened an issue at Vibe.d

Provide convenience aliases for put, delete

Hey there,

It's great to see your nice library improving over time :)
When interacting with REST APIs using PUT and DELETE is quite common.

Would you accept a PR that adds convenience alias:

  1. to Request, e.g. alias put = exec!"PUT", ...
  2. globally e.g. putContent, deleteContent

Не получается отправить данные в ArangoDB

Не могу разобраться как данные в БД отправить (ArangoDB).

вот такой запрос для CURL работает нормально:
echo {"sdf":"dima"} | curl -X POST --data-binary @- --dump - http://localhost:8529/_db/onlinetest/_api/document/?collection=sitetestanswers

onlinetest - имя БД
sitetestanswers - коллекции

Делаю тоже самое с помощью твоего кода:

    string collectionUrl = "http://localhost:8529/_db/onlinetest/_api/document/?collection=sitetestanswers";
    res.writeJsonBody(`{"a":"b"}`);
    auto rq = Request();
    auto rs = rq.post(collectionUrl, `{"a":"b", "c":[1,2,3]}`, "application/json");

В итоге вообще никакого результата не получаю.

В какую сторону копать то можно?

MultipartForm didn't working in struct scope

Hello 😃

struct Link{
	uint id;
	string title;
	string name;
	string url;
}

struct ComicsPage{
 	private:
 		Link childLink;

    void foo(){
        MultipartForm form;
        form.add(formData("password", "abc123"));
        auto d = postContent(this.childLink.url, form);
        string html_text = text(d);

        if( html_text.indexOf("name=\"password\"") >-1 ){
            writeln("Retry it");
            MultipartForm form1;
            form1.add(formData("password", "abc123"));
            auto d1 = postContent("http://somethinglink.org", form1); // childLink & "http://somethinglink.org" is same but result is not same :-(
            html_text = text(d1);
        }
    }

}

Same url and same formdata but always pass to 'Retry it'.
Very, very strange problem. I try hard to find my mistake but coudn't find it...

1.0.0-dev roadmap

Hello!

@wilzbach , here is roadmap for 1.0.0-dev:

  1. Rewrite Buffer. Use struct for container, reduce allocations. - already done, wery good results. May break some applications as Buffer now use immutable(ubyte)[] instead ubyte[]
  2. add high-level put,delete API using putContent, delContent and Request.put, Request.del - already done. Add handling "delete" request for FTP.
  3. Find and mark deprecated duplicated API's like get(url, string[string]) and get(url, QueryParams[])
  4. Write command-line utility like curl or wget to demonstrate API usage.

If anybody have any comments, additions, etc - you are welcome.

HTTP/2 status

Just out of interest: do I remember correctly that you plan to implement HTTP/2?

Do you have a public roadmap?

extend discription at code.dlang.org

Now description have not no mention about ftp support. And it's newcomers may not found at code.dlang.org any mention about FTP.

"http client library, inspired by python-requests"

don't output log by default

When I make a simple HTTP request I am greeted by this armada of log messages.
Seeing the log should be an opt-in feature ...

2016-05-15T13:45:12.818:http.d:setupConnection:469 Set up new connection
2016-05-15T13:45:12.818:streams.d:connect:703 Create connection to api.github.com:443
2016-05-15T13:45:12.819:streams.d:connect:713 Trying 192.30.252.125:443
2016-05-15T13:45:13.202:streams.d:connect:718 Connected to 192.30.252.125:443
2016-05-15T13:45:13.202:http.d:exec:946 GET /repos/dlang/phobos/commits?path=std/algorithm/searching.d HTTP/1.1
Connection: Close
User-Agent: dlang-requests
Accept-Encoding: gzip, deflate
Host: api.github.com:443


2016-05-15T13:45:13.388:http.d:receiveResponse:517 read: 1370
2016-05-15T13:45:13.388:streams.d:put:383 Append 1370 bytes
2016-05-15T13:45:13.388:streams.d:put:383 Append 1138 bytes
2016-05-15T13:45:13.389:streams.d:put:383 Append 228 bytes
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:365 statusLine: HTTP/1.1 200 OK
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header server = GitHub.com
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header date = Sun, 15 May 2016 10:45:13 GMT
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header content-type = application/json; charset=utf-8
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header transfer-encoding = chunked
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header connection = close
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header status = 200 OK
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header x-ratelimit-limit = 60
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header x-ratelimit-remaining = 47
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header x-ratelimit-reset = 1463309775
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header cache-control = public, max-age=60, s-maxage=60
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header vary = Accept
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header etag = W/"52ca99ae191db09ad0420ac8dc936a6f"
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header last-modified = Thu, 12 May 2016 15:29:49 GMT
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header x-github-media-type = github.v3; format=json
2016-05-15T13:45:13.389:http.d:parseResponseHeaders:396 Header link = <https://api.github.com/repositories/1257084/commits?path=std%2Falgorithm%2Fsearching.d&page=2>; rel="next"
2016-05-15T13:45:13.390:http.d:parseResponseHeaders:396 Header access-control-expose-headers = ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
2016-05-15T13:45:13.390:http.d:parseResponseHeaders:396 Header access-control-allow-origin = *
2016-05-15T13:45:13.390:http.d:parseResponseHeaders:396 Header content-security-policy = default-src 'none'
2016-05-15T13:45:13.390:http.d:parseResponseHeaders:396 Header strict-transport-security = max-age=31536000; includeSubdomains; preload
2016-05-15T13:45:13.390:http.d:parseResponseHeaders:396 Header x-content-type-options = nosniff
2016-05-15T13:45:13.390:http.d:parseResponseHeaders:396 Header x-frame-options = deny
2016-05-15T13:45:13.390:http.d:parseResponseHeaders:396 Header x-xss-protection = 1; mode=block
2016-05-15T13:45:13.390:http.d:parseResponseHeaders:396 Header vary = Accept, Accept-Encoding
2016-05-15T13:45:13.390:http.d:parseResponseHeaders:396 Header x-served-by = a6882e5cd2513376cb9481dbcd83f3a2
2016-05-15T13:45:13.390:http.d:parseResponseHeaders:396 Header content-encoding = gzip
2016-05-15T13:45:13.390:http.d:parseResponseHeaders:396 Header x-github-request-id = 4DF6C08C:12719:31915C3:57385339
2016-05-15T13:45:13.390:http.d:analyzeHeaders:339 transferEncoding: chunked
2016-05-15T13:45:13.390:streams.d:put:240 Got chunk size 8534
2016-05-15T13:45:13.390:streams.d:put:383 Append 222 bytes
2016-05-15T13:45:13.391:streams.d:put:257 Unchunked 222 bytes from 8534
2016-05-15T13:45:13.391:streams.d:empty:292 empty=0
2016-05-15T13:45:13.391:streams.d:empty:292 empty=1
2016-05-15T13:45:13.391:streams.d:put:383 Append 267 bytes
2016-05-15T13:45:13.391:streams.d:empty:161 empty=0
2016-05-15T13:45:13.391:streams.d:empty:161 empty=1
2016-05-15T13:45:13.391:streams.d:put:383 Append 267 bytes
2016-05-15T13:45:13.391:http.d:receiveResponse:580 read: 1370
2016-05-15T13:45:13.391:streams.d:put:383 Append 1370 bytes
2016-05-15T13:45:13.391:streams.d:put:257 Unchunked 1370 bytes from 8534
2016-05-15T13:45:13.391:streams.d:empty:292 empty=0
2016-05-15T13:45:13.391:streams.d:empty:292 empty=1
2016-05-15T13:45:13.391:streams.d:put:383 Append 444 bytes
2016-05-15T13:45:13.391:streams.d:empty:161 empty=0
2016-05-15T13:45:13.391:streams.d:empty:161 empty=1
2016-05-15T13:45:13.392:streams.d:put:383 Append 444 bytes
2016-05-15T13:45:13.392:streams.d:put:383 Append 711 bytes
2016-05-15T13:45:13.392:http.d:receiveResponse:588 receivedTotal: 1598, contentLength: -1, bodyLength: 711
2016-05-15T13:45:13.392:http.d:receiveResponse:580 read: 1370
2016-05-15T13:45:13.392:streams.d:put:383 Append 1370 bytes
2016-05-15T13:45:13.392:streams.d:put:257 Unchunked 1370 bytes from 8534
2016-05-15T13:45:13.392:streams.d:empty:292 empty=0
2016-05-15T13:45:13.392:streams.d:empty:292 empty=1
2016-05-15T13:45:13.392:streams.d:put:383 Append 2894 bytes
2016-05-15T13:45:13.392:streams.d:empty:161 empty=0
2016-05-15T13:45:13.392:streams.d:empty:161 empty=1
2016-05-15T13:45:13.392:streams.d:put:383 Append 2894 bytes
2016-05-15T13:45:13.392:streams.d:put:383 Append 2894 bytes
2016-05-15T13:45:13.392:http.d:receiveResponse:588 receivedTotal: 2968, contentLength: -1, bodyLength: 3605
2016-05-15T13:45:13.392:http.d:receiveResponse:580 read: 1370
2016-05-15T13:45:13.392:streams.d:put:383 Append 1370 bytes
2016-05-15T13:45:13.393:streams.d:put:257 Unchunked 1370 bytes from 8534
2016-05-15T13:45:13.393:streams.d:empty:292 empty=0
2016-05-15T13:45:13.393:streams.d:empty:292 empty=1
2016-05-15T13:45:13.393:streams.d:put:383 Append 4558 bytes
2016-05-15T13:45:13.393:streams.d:empty:161 empty=0
2016-05-15T13:45:13.393:streams.d:empty:161 empty=1
2016-05-15T13:45:13.393:streams.d:put:383 Append 4558 bytes
2016-05-15T13:45:13.393:streams.d:put:383 Append 4558 bytes
2016-05-15T13:45:13.393:http.d:receiveResponse:588 receivedTotal: 4338, contentLength: -1, bodyLength: 8163
2016-05-15T13:45:13.393:http.d:receiveResponse:580 read: 1370
2016-05-15T13:45:13.393:streams.d:put:383 Append 1370 bytes
2016-05-15T13:45:13.393:streams.d:put:257 Unchunked 1370 bytes from 8534
2016-05-15T13:45:13.393:streams.d:empty:292 empty=0
2016-05-15T13:45:13.393:streams.d:empty:292 empty=1
2016-05-15T13:45:13.393:streams.d:put:383 Append 6400 bytes
2016-05-15T13:45:13.393:streams.d:empty:161 empty=0
2016-05-15T13:45:13.394:streams.d:empty:161 empty=1
2016-05-15T13:45:13.394:streams.d:put:383 Append 6400 bytes
2016-05-15T13:45:13.394:streams.d:put:383 Append 6400 bytes
2016-05-15T13:45:13.394:http.d:receiveResponse:588 receivedTotal: 5708, contentLength: -1, bodyLength: 14563
2016-05-15T13:45:13.394:http.d:receiveResponse:580 read: 1370
2016-05-15T13:45:13.394:streams.d:put:383 Append 1370 bytes
2016-05-15T13:45:13.394:streams.d:put:257 Unchunked 1370 bytes from 8534
2016-05-15T13:45:13.394:streams.d:empty:292 empty=0
2016-05-15T13:45:13.394:streams.d:empty:292 empty=1
2016-05-15T13:45:13.394:streams.d:put:383 Append 8224 bytes
2016-05-15T13:45:13.394:streams.d:empty:161 empty=0
2016-05-15T13:45:13.394:streams.d:empty:161 empty=1
2016-05-15T13:45:13.394:streams.d:put:383 Append 8224 bytes
2016-05-15T13:45:13.394:streams.d:put:383 Append 8224 bytes
2016-05-15T13:45:13.394:http.d:receiveResponse:588 receivedTotal: 7078, contentLength: -1, bodyLength: 22787
2016-05-15T13:45:13.394:http.d:receiveResponse:580 read: 1370
2016-05-15T13:45:13.394:streams.d:put:383 Append 1370 bytes
2016-05-15T13:45:13.395:streams.d:put:257 Unchunked 1370 bytes from 8534
2016-05-15T13:45:13.395:streams.d:empty:292 empty=0
2016-05-15T13:45:13.395:streams.d:empty:292 empty=1
2016-05-15T13:45:13.395:streams.d:put:383 Append 8992 bytes
2016-05-15T13:45:13.395:streams.d:empty:161 empty=0
2016-05-15T13:45:13.395:streams.d:empty:161 empty=1
2016-05-15T13:45:13.395:streams.d:put:383 Append 8992 bytes
2016-05-15T13:45:13.395:streams.d:put:383 Append 8992 bytes
2016-05-15T13:45:13.395:http.d:receiveResponse:588 receivedTotal: 8448, contentLength: -1, bodyLength: 31779
2016-05-15T13:45:13.395:http.d:receiveResponse:580 read: 99
2016-05-15T13:45:13.395:streams.d:put:383 Append 92 bytes
2016-05-15T13:45:13.395:streams.d:put:257 Unchunked 92 bytes from 8534
2016-05-15T13:45:13.395:streams.d:put:259 switch to huntig separator
2016-05-15T13:45:13.395:streams.d:put:278 switch to huntig size
2016-05-15T13:45:13.395:streams.d:put:240 Got chunk size 0
2016-05-15T13:45:13.395:streams.d:put:246 Unchunk completed
2016-05-15T13:45:13.395:streams.d:empty:292 empty=0
2016-05-15T13:45:13.395:streams.d:empty:292 empty=1
2016-05-15T13:45:13.396:streams.d:put:383 Append 10216 bytes
2016-05-15T13:45:13.396:streams.d:empty:161 empty=0
2016-05-15T13:45:13.396:streams.d:empty:161 empty=1
2016-05-15T13:45:13.396:streams.d:put:383 Append 10216 bytes
2016-05-15T13:45:13.396:streams.d:put:383 Append 10216 bytes
2016-05-15T13:45:13.396:http.d:receiveResponse:588 receivedTotal: 8547, contentLength: -1, bodyLength: 41995
2016-05-15T13:45:13.396:streams.d:empty:292 empty=1
2016-05-15T13:45:13.396:streams.d:put:383 Append 47035 bytes
2016-05-15T13:45:13.396:streams.d:empty:161 empty=0
2016-05-15T13:45:13.396:streams.d:empty:161 empty=1
2016-05-15T13:45:13.396:streams.d:put:383 Append 47035 bytes
2016-05-15T13:45:13.396:streams.d:put:383 Append 47035 bytes
2016-05-15T13:45:13.396:http.d:exec:975 Closing connection because of 'Connection: close' or no 'Connection' header
2016-05-15T13:45:13.397:streams.d:close:693 Close socket

add toString for HTTPRequest and HTTPResponse

How about adding nice toString for HTTPRequest and HTTPResponse?

  • HTTPRequest contains a lot of redundant info which makes it hard to log at for convenient debugging
  • HTTPReponse just prints its name atm
import std.stdio;
auto rq = Request();
Response rs = rq.post("...");
writeln(rq); // Request(URI("<url>", "https", "", "", 443, "github.com", "<url>"), HTTPRequest("POST", URI("<url>", "https", "", "", 443, "<host>", "<path>", "<query>"), [], [], null, true, 10, 32768, 0, "", 0, 30 secs, 12288, false, requests.streams.SSLVibeStream, [], null, null, -1, 69, [Tuple!(string, "path", string, "domain", string, "attr", string, "value")("/", "<host>", "<cookie-name>", "<cookie-val>")], SSLOptions(false, "", "", "", pem, pem), requests.http.HTTPResponse), FTPRequest(URI("", "", "", "", 80, "", "/", ""), 1 minute, 0, 8192, 1073741824, -1, 0, null, [], null, false, null))
writeln(rs); // requests.http.HTTPResponse 

See also: https://wiki.dlang.org/Defining_custom_print_format_specifiers

It should provide a way to pass non-unique params

Query params could be not unique. For example this is a valid way to pass an array:

http://example.com?name[]=hello&name[]=world

So an associative array string[string] can't handle this case.

Check this:

alias QueryParam = Tuple!(string, "key", string, "value");

auto queryParams(T...)(T params)
{
   static assert (T.length % 2 == 0, "wrong args count");

   QueryParam[] output;
   output.reserve = T.length / 2;

   void queryParamsHelper(T...)(T params, ref QueryParam[] output)
   {
      static if (T.length > 0)
      {
         output ~= QueryParam(params[0].to!string, params[1].to!string);
         queryParamsHelper(params[2..$], output);
      }
   }

   queryParamsHelper(params, output);
   return output;
}

So you can write:

rs = rq.get("http://httpbin.org/get", queryParams("name[]", "first", "name[]", "another"));

but also:

// Automagic to!string
rs = rq.get("http://httpbin.org/get", queryParams("number", 3, "hello", "world"));

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.