尝试在 Java 中使用 HttpClient 发送 HTTP Post GraphQL 查询时出现 HTTP 400

Posted

技术标签:

【中文标题】尝试在 Java 中使用 HttpClient 发送 HTTP Post GraphQL 查询时出现 HTTP 400【英文标题】:HTTP 400 When Trying to Send HTTP Post GraphQL Query using HttpClient in Java 【发布时间】:2018-10-18 07:07:00 【问题描述】:

对外部 GraphQL 服务器进行后查询,该服务器在 Postman Pro 设置中作为预请求脚本工作。

我正在尝试使用 HttpClient 访问外部 GraphQL 服务器。

Maven 依赖 pom.xml:

<!-- Apache Commons -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.6</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.2.5</version>
</dependency>

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.6</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.2.4</version>
</dependency>

<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.1</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient-cache</artifactId>
    <version>4.2.5</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
    <version>4.2.5</version>
</dependency>

<!-- Google Code -->
<dependency>
    <groupId>com.googlecode.json-simple</groupId>
    <artifactId>json-simple</artifactId>
    <version>1.1.1</version>
</dependency>

将预请求脚本硬编码如下:

GetDataAsRequest.java

public class GetDataAsRequest 
    private static final String GET_DATA_REQUEST_TEMPLATE = 
    "query " 
        + "    viewer      "
        + "        data(id: \"10045701-2017-41a4-33f4-bf8481cf01dc\") " + 
        "            id"
        + "            createdDate" 
        + "            lastModifiedDate" 
        + "            abbreviation"
        + "            cityStateRegion" 
        + "        "
        + "    "
        + "";
    

    public static String generateQuery() 
        return String.format(GET_DATA_REQUEST_TEMPLATE);
    

我的控制器:

public class MyController 

    private static final String EXTERNAL_API_URL = "https://sample-api.com"

    public static void main (String args []) 

        // Generate Access Token
        String accessToken = OAuth2Client.generateAccessToken();
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpPost postRequest = new HttpPost(EXTERNAL_API_URL);
        StringEntity postBody = new StringEntity(GetDataAsRequest.generateQuery());
        postBody.setContentType("application/json");
        postRequest.addHeader("Authorization", "Bearer " + accessToken);
        postRequest.setEntity(postBody);
        HttpResponse response = httpClient.execute(postRequest);

        if (response.getStatusLine().getStatusCode() != 201) 
            throw new RuntimeException("Failed : HTTP error code : " + response.getStatusLine().getStatusCode());
        

        BufferedReader br = new BufferedReader(new InputStreamReader((response.getEntity().getContent())));

        String output;
        System.out.println("Output from Server .... \n");
        while ((output = br.readLine()) != null) 
            System.out.println(output);
        
        httpClient.getConnectionManager().shutdown();
         
        catch (MalformedURLException e) 
            e.printStackTrace();
         
        catch (IOException e) 
            e.printStackTrace();
        
    

虽然当我将其从标准输入(控制台)复制到 Postman 调用时,相同的 OAuthToken 和 HTTP 发布查询有效,但在 Java 程序中尝试使用 HttpClient 模拟相同的调用时,我不断收到 HTTP 400:

03:25:30.690 [main] DEBUG org.apache.http.impl.conn.BasicClientConnectionManager - Get connection for route s->https://https://sample-api.com
03:25:30.751 [main] DEBUG org.apache.http.impl.conn.DefaultClientConnectionOperator - Connecting to https://sample-api.com:443
03:25:30.943 [main] DEBUG org.apache.http.client.protocol.RequestAddCookies - CookieSpec selected: best-match
03:25:30.950 [main] DEBUG org.apache.http.client.protocol.RequestAuthCache - Auth cache not set in the context
03:25:30.950 [main] DEBUG org.apache.http.client.protocol.RequestTargetAuthentication - Target auth state: UNCHALLENGED
03:25:30.951 [main] DEBUG org.apache.http.client.protocol.RequestProxyAuthentication - Proxy auth state: UNCHALLENGED
03:25:30.951 [main] DEBUG org.apache.http.impl.client.DefaultHttpClient - Attempt 1 to execute request
03:25:30.951 [main] DEBUG org.apache.http.impl.conn.DefaultClientConnection - Sending request: POST /v1/oauth/token HTTP/1.1
03:25:30.951 [main] DEBUG org.apache.http.wire - >> "POST /v1/oauth/token HTTP/1.1[\r][\n]"
03:25:30.952 [main] DEBUG org.apache.http.wire - >> "Content-Length: 119[\r][\n]"
03:25:30.952 [main] DEBUG org.apache.http.wire - >> "Content-Type: application/x-www-form-urlencoded; charset=UTF-8[\r][\n]"
03:25:30.952 [main] DEBUG org.apache.http.wire - >> "Host: https://sample-api.com[\r][\n]"
03:25:30.952 [main] DEBUG org.apache.http.wire - >> "Connection: Keep-Alive[\r][\n]"
03:25:30.952 [main] DEBUG org.apache.http.wire - >> "User-Agent: Apache-HttpClient/4.2.5 (java 1.5)[\r][\n]"
03:25:30.952 [main] DEBUG org.apache.http.wire - >> "[\r][\n]"
03:25:30.952 [main] DEBUG org.apache.http.headers - >> POST /v1/oauth/token HTTP/1.1
03:25:30.952 [main] DEBUG org.apache.http.headers - >> Content-Length: 119
03:25:30.952 [main] DEBUG org.apache.http.headers - >> Content-Type: application/x-www-form-urlencoded; charset=UTF-8
03:25:30.952 [main] DEBUG org.apache.http.headers - >> Host: https://sample-api.com
03:25:30.952 [main] DEBUG org.apache.http.headers - >> Connection: Keep-Alive
03:25:30.953 [main] DEBUG org.apache.http.headers - >> User-Agent: Apache-HttpClient/4.2.5 (java 1.5)
03:25:30.953 [main] DEBUG org.apache.http.wire - >> "grant_type=client_credentials&client_id=fghuXeULFBdW4B1dmRY0MhROMRQnlumk&client_secret=MM2banXEq2R1GhRvIQ2d2AKRx0SORvb4"
03:25:31.132 [main] DEBUG org.apache.http.wire - << "HTTP/1.1 200 OK[\r][\n]"
03:25:31.134 [main] DEBUG org.apache.http.wire - << "X-Application-Context: application:prod[\r][\n]"
03:25:31.134 [main] DEBUG org.apache.http.wire - << "Content-Type: application/json[\r][\n]"
03:25:31.134 [main] DEBUG org.apache.http.wire - << "Cache-Control: no-store[\r][\n]"
03:25:31.134 [main] DEBUG org.apache.http.wire - << "Pragma: no-cache[\r][\n]"
03:25:31.134 [main] DEBUG org.apache.http.wire - << "Accept-Ranges: bytes[\r][\n]"
03:25:31.135 [main] DEBUG org.apache.http.wire - << "X-Cache-Hits: 0[\r][\n]"
03:25:31.135 [main] DEBUG org.apache.http.wire - << "X-Timer: S1525774921.421988,VS0,VE41[\r][\n]"
03:25:31.135 [main] DEBUG org.apache.http.wire - << "Date: Tue, 08 May 2018 10:22:01 GMT[\r][\n]"
03:25:31.135 [main] DEBUG org.apache.http.wire - << "Content-Length: 325[\r][\n]"
03:25:31.135 [main] DEBUG org.apache.http.wire - << "Connection: keep-alive[\r][\n]"
03:25:31.135 [main] DEBUG org.apache.http.wire - << "X-Sample-APP: sso[\r][\n]"
03:25:31.135 [main] DEBUG org.apache.http.wire - << "Access-Control-Allow-Headers: Authorization,Content-Type[\r][\n]"
03:25:31.135 [main] DEBUG org.apache.http.wire - << "Access-Control-Allow-Methods: PUT,POST,OPTIONS,GET,PATCH,DELETE[\r][\n]"
03:25:31.135 [main] DEBUG org.apache.http.wire - << "Access-Control-Allow-Origin: *[\r][\n]"
03:25:31.135 [main] DEBUG org.apache.http.wire - << "[\r][\n]"
03:25:31.136 [main] DEBUG org.apache.http.impl.conn.DefaultClientConnection - Receiving response: HTTP/1.1 200 OK
03:25:31.136 [main] DEBUG org.apache.http.headers - << HTTP/1.1 200 OK
03:25:31.136 [main] DEBUG org.apache.http.headers - << X-Application-Context: application:prod
03:25:31.136 [main] DEBUG org.apache.http.headers - << Content-Type: application/json
03:25:31.136 [main] DEBUG org.apache.http.headers - << Cache-Control: no-store
03:25:31.136 [main] DEBUG org.apache.http.headers - << Pragma: no-cache
03:25:31.138 [main] DEBUG org.apache.http.headers - << Accept-Ranges: bytes
03:25:31.138 [main] DEBUG org.apache.http.headers - << X-Served-By: cache-sample-las9320
03:25:31.138 [main] DEBUG org.apache.http.headers - << X-Cache-Hits: 0
03:25:31.138 [main] DEBUG org.apache.http.headers - << X-Timer: S1525774921.421988,VS0,VE41
03:25:31.138 [main] DEBUG org.apache.http.headers - << Date: Tue, 08 May 2018 10:22:01 GMT
03:25:31.138 [main] DEBUG org.apache.http.headers - << Content-Length: 325
03:25:31.138 [main] DEBUG org.apache.http.headers - << Connection: keep-alive
03:25:31.138 [main] DEBUG org.apache.http.headers - << X-Sample-APP: sso
03:25:31.138 [main] DEBUG org.apache.http.headers - << Access-Control-Allow-Headers: Authorization,Content-Type
03:25:31.138 [main] DEBUG org.apache.http.headers - << Access-Control-Allow-Methods: PUT,POST,OPTIONS,GET,PATCH,DELETE
03:25:31.138 [main] DEBUG org.apache.http.headers - << Access-Control-Allow-Origin: *
03:25:31.141 [main] DEBUG org.apache.http.impl.client.DefaultHttpClient - Connection can be kept alive indefinitely
03:25:31.147 [main] DEBUG org.apache.http.wire - << ""access_token":"fkMmGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6bnVsbCwiZGV2aWNlSWQiOm51bGwsImNsaWVudElkIjoiaUxIdVhlVUxGQmRXNEIxZG1SWTBNaFJPTVJRbmxmZUsiLCJhZElkIjpudWxsLCJleHAiOjE1MjU3Nzg1MjEsImlhdCI6MTUyNTc3NDkyMX0.U3gacgf7a8hlwCuCn6yGXc0z6E5Zrwgii3B_77xFlyo","token_type":"Bearer","expires_in":3600,"refresh_token":null,"scope":null"
03:25:31.147 [main] DEBUG org.apache.http.impl.conn.BasicClientConnectionManager - Releasing connection org.apache.http.impl.conn.ManagedClientConnectionImpl@2ddc8ecb
03:25:31.147 [main] DEBUG org.apache.http.impl.conn.BasicClientConnectionManager - Connection can be kept alive indefinitely
Exception in thread "main" java.lang.RuntimeException: Failed : HTTP error code : 400

返回 HTTP 400 是否是因为我的请求正文在某些方面有误?

有没有更简单的方法通过 GraphQL 的 Java 库获取 JSON 响应:

<!-- GraphQL -->
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java</artifactId>
    <version>8.0</version>
</dependency>

<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java-tools</artifactId>
    <version>5.0.0</version>
</dependency>

互联网上有很多例子展示了如何设置和外部的 GraphQL 服务器,我需要学习如何使用 GraphQL Java 库作为客户端库来解析并从外部服务器获取正确的 GraphQL 响应。 ..

这从 Postman Pro 和 curl 正确返回,但不使用 HttpClient。

我可能做错了什么?

【问题讨论】:

【参考方案1】:

你控制服务器吗?如果是这样,为什么不在那里放置一个断点并检查失败的地方?如果您不控制服务器,我会说仔细检查您在此处发送的内容与使用 Postman 发送的内容之间的差异。

一件事立刻看起来很可疑。您发送的 POST 声称具有 JSON 正文,但您发送的字符串是原始 GraphQL 查询,根本不是 JSON。

我可以告诉您将请求正文转换为 GraphQL over HTTP Spec 定义的 JSON 对象,但在不知道您的服务器是否实际实现此规范的情况下,这只是猜测。

规范定义为 POST 请求的正确主体的是具有 3 个字段的 JSON 对象:queryoperationNamevariablesquery 字段应包含查询 string (因此首先对查询进行字符串化),operationName 包含在有多个时要执行的操作的名称(与您的情况无关),以及 @ 987654327@ 包含一个 JSON 对象,其中变量作为键值对(现在不相关)。

在您的情况下,正文应如下所示:


    query: "
        viewer 
            data(id: \"10045701-2017-41a4-33f4-bf8481cf01dc\") 
                id
                createdDate
                lastModifiedDate
                abbreviation
                cityStateRegion
            
        
    "

要将这个字符串放入 Java 代码中,所有这些都需要额外的转义级别,但我希望您的 IDE 能够处理好这一点。还要注意换行符很重要。如果您因任何原因丢失它们,您必须在所选字段之间放置逗号,以使查询保持有效。

【讨论】:

以上是关于尝试在 Java 中使用 HttpClient 发送 HTTP Post GraphQL 查询时出现 HTTP 400的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android 和/或 Java 中使用 HttpClient 管理 cookie?

找不到 SSL 证书的 Java HttpClient 错误,在代码中使用证书作为字符串?

httpclient发邮件

使用 HttpClient 和 Web API 方法 [FromBody] 参数发布到 Web API 最终为空

HTTPClient-java.net.SocketException:未实现未连接的套接字抛出

在 Java 11 及更高版本中使用 HttpClient 时如何跟踪 HTTP 303 状态代码?