Skip to main content
编辑本页

Vert.x Web Client(Web客户端)是一个异步的 HTTP 和 HTTP/2 客户端。

Web Client使得发送 HTTP 请求以及从 Web 服务器接收 HTTP 响应变得更加便捷,同时提供了额外的高级功能,例如:

  • JSON体的编码和解码

  • 请求和响应泵

  • 请求参数的处理

  • 统一的错误处理

  • 提交表单

制作Web Client的目的并非为了替换Vert.x Core中的 HttpClient , 而是基于该客户端,扩展并保留其便利的设置和特性,例如请求连接池(Pooling),HTTP/2的支持, 流水线/管线的支持等。当您需要对 HTTP 请求和响应做细微粒度控制时,您应当使用 HttpClient

另外Web Client并未提供 WebSocket API,此时您应当使用 HttpClient

使用Web Client

如需使用Vert.x Web Client,请先加入以下依赖:

  • Maven (在 pom.xml 文件中):

<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-web-client</artifactId>
 <version>3.6.2</version>
</dependency>
  • Gradle (在 build.gradle 文件中):

dependencies {
 compile 'io.vertx:vertx-web-client:3.6.2'
}

对Vert.x Core HTTP Client的回顾

Vert.x Web Client使用Vert.x Core的API,如您对此还不熟悉,请先熟悉 HttpClient 的一些基本概念。

创建Web Client

您可使用缺省设置创建一个 WebClient

<?php
    use io\vertx\jphp\ext\web\client\WebClient;
    $client = WebClient::create($vertx);

您亦可使用配置选项来创建客户端:

<?php
    use io\vertx\jphp\ext\web\client\WebClient;
    $options = array(
        "userAgent" => "My-App/1.2.3"
    );
    $options["keepAlive"] = false;
    $client = WebClient::create($vertx, $options);

Web Client配置选项继承自 HttpClient 配置选项,使用时可根据实际情况选择。

如已在程序中创建 HttpClient,可用以下方式复用:

<?php
    use io\vertx\jphp\ext\web\client\WebClient;
    $client = WebClient::wrap($httpClient);

发送请求

无请求体的简单请求

一般情况下,HTTP GET,OPTIONS以及HEAD请求没有请求体,可用以下方式发送无请求体的HTTP Requests(HTTP请求):

<?php
    use io\vertx\jphp\ext\web\client\WebClient;

    $client = WebClient::create($vertx);

    // Send a GET request
    $client->get(8080, "myserver.mycompany.com", "/some-uri")->send(function ($ar, $ar_err) {
        if ($ar != null) {
            // Obtain response
            $response = $ar;

            echo "Received response with status code".$response->statusCode()."\n";
        } else {
            echo "Something went wrong ".$ar_err->getMessage()."\n";
        };
    });

    // Send a HEAD request
    $client->head(8080, "myserver.mycompany.com", "/some-uri")->send(function ($ar, $ar_err) {
        if ($ar != null) {
            // Obtain response
            $response = $ar;

            echo "Received response with status code".$response->statusCode()."\n";
        } else {
            echo "Something went wrong ".$ar_err->getMessage()."\n";
        };
    });

您可用以下链式方式向请求URI添加查询参数

<?php
    $client->get(8080, "myserver.mycompany.com", "/some-uri")->addQueryParam("param", "param_value")->send(function ($ar, $ar_err) {
        if ($ar != null) {
            // Obtain response
            $response = $ar;

            echo "Received response with status code".$response->statusCode()."\n";
        } else {
            echo "Something went wrong ".$ar_err->getMessage()."\n";
        };
    });

在请求URI中的参数将会被预填充

<?php
    $request = $client->get(8080, "myserver.mycompany.com", "/some-uri?param1=param1_value&param2=param2_value");

    // Add param3
    $request->addQueryParam("param3", "param3_value");

    // Overwrite param2
    $request->setQueryParam("param2", "another_param2_value");

设置请求URI将会自动清除已有的查询参数

<?php
    $request = $client->get(8080, "myserver.mycompany.com", "/some-uri");

    // Add param1
    $request->addQueryParam("param1", "param1_value");

    // Overwrite param1 and add param2
    $request->uri("/some-uri?param1=param1_value&param2=param2_value");

填充请求体

如需要发送请求体,可使用相同的API并在最后加上 sendXXX 方法发送相应的请求体。

例如用 sendBuffer 方法发送一个缓冲体:

<?php
    // Send a buffer to the server using POST, the content-length header will be set for you
    $client->post(8080, "myserver.mycompany.com", "/some-uri")->sendBuffer($buffer, function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        };
    });

有时候我们并不希望将所有数据一次性全部读入内存,因为文件太大或希望同时处理多个请求,希望每个请求仅使用最小的内存。出于此目的,Web Client可用 sendStream 方法发送流式数据 ReadStream<Buffer> (例如 AsyncFile 便是一个 ReadStream<Buffer>):

<?php
    // When the stream len is unknown sendStream sends the file to the server using chunked transfer encoding
    $client->post(8080, "myserver.mycompany.com", "/some-uri")->sendStream($stream, function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        };
    });

Web Client会为您设置好传输泵以平滑传输流。如果流长度未知则使用分块传输(chunked transfer)。

如已知流的大小,可在HTTP协议头中设置 content-length 属性

<?php
    $fs->open("content.txt", array(
    ), function ($fileRes, $fileRes_err) {
        if ($fileRes != null) {
            $fileStream = $fileRes;

            $fileLen = "1024";

            // Send the file to the server using POST
            $client->post(8080, "myserver.mycompany.com", "/some-uri")->putHeader("content-length", $fileLen)->sendStream($fileStream, function ($ar, $ar_err) {
                if ($ar != null) {
                    // Ok
                };
            });
        };
    });

此时POST方法不会使用分块传输。

JSON体

有时您需要在请求体中使用JSON格式,可使用 sendJsonObject 方法发送 JsonObject

<?php
    $client->post(8080, "myserver.mycompany.com", "/some-uri")->sendJsonObject(array(
        "firstName" => "Dale",
        "lastName" => "Cooper"
    ), function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        };
    });

在Java,Groovy以及Kotlin语言中,您亦可使用 sendJson 方法发送POJO(Plain Old Java Object),该方法会自动调用 Json::encode 方法将 POJO 映射为 JSON:

<?php
    $client->post(8080, "myserver.mycompany.com", "/some-uri")->sendJson(Java::type("examples.WebClientExamples.User").newInstance("Dale", "Cooper"), function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        };
    });
Note
Json::encode 方法使用Jackson的 mapper将 POJO 映射成 JSON。

表单提交

您可使用 sendForm 方法发送HTTP表单。

<?php
    use io\vertx\jphp\core\MultiMap;
    $form = MultiMap::caseInsensitiveMultiMap();
    $form->set("firstName", "Dale");
    $form->set("lastName", "Cooper");

    // Submit the form as a form URL encoded body
    $client->post(8080, "myserver.mycompany.com", "/some-uri")->sendForm($form, function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        };
    });

缺省情况下,提交表单的请求头中的 content-type 属性值为 application/x-www-form-urlencoded,您亦可将其设置为 multipart/form-data

<?php
    use io\vertx\jphp\core\MultiMap;
    $form = MultiMap::caseInsensitiveMultiMap();
    $form->set("firstName", "Dale");
    $form->set("lastName", "Cooper");

    // Submit the form as a multipart form body
    $client->post(8080, "myserver.mycompany.com", "/some-uri")->putHeader("content-type", "multipart/form-data")->sendForm($form, function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        };
    });

If you want to upload files and send attributes, you can create a MultipartForm and use sendMultipartForm.

<?php
    use io\vertx\jphp\ext\web\multipart\MultipartForm;
    $form = MultipartForm::create()->attribute("imageDescription", "a very nice image")->binaryFileUpload("imageFile", "image.jpg", "/path/to/image", "image/jpeg");

    // Submit the form as a multipart form body
    $client->post(8080, "myserver.mycompany.com", "/some-uri")->sendMultipartForm($form, function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        };
    });

填充请求头

您可使用以下方式填充请求头:

<?php
    $request = $client->get(8080, "myserver.mycompany.com", "/some-uri");
    $headers = $request->headers();
    $headers->set("content-type", "application/json");
    $headers->set("other-header", "foo");

此处 Headers 是一个 MultiMap 对象,提供了增加、设置以及删除头属性操作的入口。HTTP头的某些特定属性允许设置多个值。

您亦可通过 putHeader 方法写入头属性:

<?php
    $request = $client->get(8080, "myserver.mycompany.com", "/some-uri");
    $request->putHeader("content-type", "application/json");
    $request->putHeader("other-header", "foo");

重用请求

send 方法可被重复多次调用,这使得配置以及重用 HttpRequest 对象变得更加便捷:

<?php
    $get = $client->get(8080, "myserver.mycompany.com", "/some-uri");
    $get->send(function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        };
    });

    // Same request again
    $get->send(function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        };
    });

请注意, HttpRequest 对象是可变的。 所以在修改缓存中的对象之前,您应当使用 copy 方法先复制一份拷贝:

<?php
    $get = $client->get(8080, "myserver.mycompany.com", "/some-uri");
    $get->send(function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        };
    });

    // The "get" request instance remains unmodified
    $get->copy()->putHeader("a-header", "with-some-value")->send(function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        };
    });

超时

您可通过 timeout 方法设置超时时间。

<?php
    $client->get(8080, "myserver.mycompany.com", "/some-uri")->timeout(5000)->send(function ($ar, $ar_err) {
        if ($ar != null) {
            // Ok
        } else {
            // Might be a timeout when cause is java.util.concurrent.TimeoutException
        };
    });

若请求在设定时间内没返回任何数据,则一个超时异常将会传递给响应处理代码。

处理HTTP响应

Web Client请求发送之后,返回的结果将会被包装在异步结果 HttpResponse 中。

当响应被成功接收到之后,相应的回调函数将会被触发。

<?php
    $client->get(8080, "myserver.mycompany.com", "/some-uri")->send(function ($ar, $ar_err) {
        if ($ar != null) {

            $response = $ar;

            echo "Received response with status code".$response->statusCode()."\n";
        } else {
            echo "Something went wrong ".$ar_err->getMessage()."\n";
        };
    });
Warning
缺省状况下,响应会被完全缓冲读入内存,请用 BodyCodec::pipe 方法将响应写入流。

响应编解码器

缺省状况下,响应以缓冲形式提供,并不提供任何形式的解码。

可用 BodyCodec 将响应定制成以下类型:

  • 普通字符串

  • JSON对象

  • 将JSON映射成POJO

  • WriteStream

响应体编解码器对二进制数据流解码,以节省您在响应处理中的代码。

使用 BodyCodec::jsonObject 将结果解码为JSON对象:

<?php
    use io\vertx\jphp\ext\web\codec\BodyCodec;
    $client->get(8080, "myserver.mycompany.com", "/some-uri")->as(BodyCodec::jsonObject())->send(function ($ar, $ar_err) {
        if ($ar != null) {
            $response = $ar;

            $body = $response->body();

            echo "Received response with status code".$response->statusCode()." with body ".$body."\n";
        } else {
            echo "Something went wrong ".$ar_err->getMessage()."\n";
        };
    });

在Java,Groovy以及Kotlin语言中,JSON对象可被解码映射成POJO:

<?php
    use io\vertx\jphp\ext\web\codec\BodyCodec;
    $client->get(8080, "myserver.mycompany.com", "/some-uri")->as(BodyCodec::json(Java::type("examples.WebClientExamples.User")->class))->send(function ($ar, $ar_err) {
        if ($ar != null) {
            $response = $ar;

            $user = $response->body();

            echo "Received response with status code".$response->statusCode()." with body ".$user->getFirstName()." ".$user->getLastName()."\n";
        } else {
            echo "Something went wrong ".$ar_err->getMessage()."\n";
        };
    });

当响应结果较大时,请使用 BodyCodec::pipe 方法。响应体编解码器将响应结果压入 WriteStream 并在最后发出成功或失败的信号。

<?php
    use io\vertx\jphp\ext\web\codec\BodyCodec;
    $client->get(8080, "myserver.mycompany.com", "/some-uri")->as(BodyCodec::pipe($writeStream))->send(function ($ar, $ar_err) {
        if ($ar != null) {

            $response = $ar;

            echo "Received response with status code".$response->statusCode()."\n";
        } else {
            echo "Something went wrong ".$ar_err->getMessage()."\n";
        };
    });

最后,如您对响应结果不感兴趣,可用 BodyCodec::none 废弃响应体。

<?php
    use io\vertx\jphp\ext\web\codec\BodyCodec;
    $client->get(8080, "myserver.mycompany.com", "/some-uri")->as(BodyCodec::none())->send(function ($ar, $ar_err) {
        if ($ar != null) {

            $response = $ar;

            echo "Received response with status code".$response->statusCode()."\n";
        } else {
            echo "Something went wrong ".$ar_err->getMessage()."\n";
        };
    });

若无法预知响应内容类型,您依旧可以在获取结果之后,用 bodyAsXXX() 方法将其转换成特定的类型

<?php
    $client->get(8080, "myserver.mycompany.com", "/some-uri")->send(function ($ar, $ar_err) {
        if ($ar != null) {

            $response = $ar;

            // Decode the body as a json object
            $body = $response->bodyAsJsonObject();

            echo "Received response with status code".$response->statusCode()." with body ".$body."\n";
        } else {
            echo "Something went wrong ".$ar_err->getMessage()."\n";
        };
    });
Warning
这种方式仅对响应结果为缓冲体有效。

处理30x重定向

缺省状况下,客户端将会依照30x状态码自动重定向,您可使用 WebClientOptions 予以配置:

<?php
    use io\vertx\jphp\ext\web\client\WebClient;

    // Change the default behavior to not follow redirects
    $client = WebClient::create($vertx, array(
        "followRedirects" => false
    ));

客户端将会执行最多达`16`次重定向,该参数亦可在 WebClientOptions 配置:

<?php
    use io\vertx\jphp\ext\web\client\WebClient;

    // Follow at most 5 redirections
    $client = WebClient::create($vertx, array(
        "maxRedirects" => 5
    ));

使用HTTPS

Vert.x Web Client可用与 HttpClient 相同方式配置HTTPS协议。

您可对每个请求单独设置:

<?php

    $client->get(443, "myserver.mycompany.com", "/some-uri")->ssl(true)->send(function ($ar, $ar_err) {
        if ($ar != null) {
            // Obtain response
            $response = $ar;

            echo "Received response with status code".$response->statusCode()."\n";
        } else {
            echo "Something went wrong ".$ar_err->getMessage()."\n";
        };
    });

或使用绝对路径:

<?php

    $client->getAbs("https://myserver.mycompany.com:4043/some-uri")->send(function ($ar, $ar_err) {
        if ($ar != null) {
            // Obtain response
            $response = $ar;

            echo "Received response with status code".$response->statusCode()."\n";
        } else {
            echo "Something went wrong ".$ar_err->getMessage()."\n";
        };
    });