Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"400 Bad Request" using simple Graph API call over https #149

Open
bbogart opened this issue Feb 4, 2023 · 5 comments
Open

"400 Bad Request" using simple Graph API call over https #149

bbogart opened this issue Feb 4, 2023 · 5 comments

Comments

@bbogart
Copy link

bbogart commented Feb 4, 2023

I've been trying to get RESTC-CPP to work with the Meta Graph API and I can't get anywhere. My GET request is simple: "https://graph.facebook.com/v15.0/me?access_token=[token]"

this works fine if I paste it right in the browser, and also using curl in a terminal:

curl -v -i -X GET "https://graph.facebook.com/v15.0/me?access_token=[token]"

I thought it may be headers, so I tried sending the headers sent by the browser and also those sent by curl.

In all cases when I try and do this in RESTC-cpp I get the error:

Caught exception: Request failed with HTTP error: 400 Bad Request

Except when I used the encoding header:

.Header("Accept-Encoding", "gzip, deflate, br")

Which results in a different error message:

Caught exception: Unsupported compression.

Here is the output of my CURL call with verbose info (token redacted):


*   Trying 2a03:2880:f001:6:face:b00c:0:2:443...
* TCP_NODELAY set
*   Trying 157.240.3.20:443...
* TCP_NODELAY set
* Connected to graph.facebook.com (157.240.3.20) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_CHACHA20_POLY1305_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=California; L=Menlo Park; O=Meta Platforms, Inc.; CN=*.facebook.com
*  start date: Jan  9 00:00:00 2023 GMT
*  expire date: Feb 13 23:59:59 2023 GMT
*  subjectAltName: host "graph.facebook.com" matched cert's "*.facebook.com"
*  issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=DigiCert SHA2 High Assurance Server CA
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x55a05ff3e2f0)
> GET /v15.0/me?access_token=[token] HTTP/2
> Host: graph.facebook.com
> user-agent: curl/7.68.0
> accept: */*
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 200 
HTTP/2 200 
< etag: "bc30657262eee6b93b9aff07f8939bc14f5cd015"
etag: "bc30657262eee6b93b9aff07f8939bc14f5cd015"
< content-type: application/json; charset=UTF-8
content-type: application/json; charset=UTF-8
< vary: Origin
vary: Origin
< x-business-use-case-usage: {"100765302885321":[{"type":"pages","call_count":1,"total_cputime":1,"total_time":1,"estimated_time_to_regain_access":0}]}
x-business-use-case-usage: {"100765302885321":[{"type":"pages","call_count":1,"total_cputime":1,"total_time":1,"estimated_time_to_regain_access":0}]}
< x-fb-rlafr: 0
x-fb-rlafr: 0
< access-control-allow-origin: *
access-control-allow-origin: *
< facebook-api-version: v15.0
facebook-api-version: v15.0
< strict-transport-security: max-age=15552000; preload
strict-transport-security: max-age=15552000; preload
< pragma: no-cache
pragma: no-cache
< cache-control: private, no-cache, no-store, must-revalidate
cache-control: private, no-cache, no-store, must-revalidate
< expires: Sat, 01 Jan 2000 00:00:00 GMT
expires: Sat, 01 Jan 2000 00:00:00 GMT
< x-fb-request-id: AelbwCDPgnsSaCUH362jVHf
x-fb-request-id: AelbwCDPgnsSaCUH362jVHf
< x-fb-trace-id: GrY4l/GOYNg
x-fb-trace-id: GrY4l/GOYNg
< x-fb-rev: 1006914144
x-fb-rev: 1006914144
< x-fb-debug: cCFxQ+O2TeqKG8GkarpBJJxbOJR+GAPPPlbK/sSTdCY1R966Eo8d3Szj61aKBkmHDHbH9t3KkjCTO0wgpV15bA==
x-fb-debug: cCFxQ+O2TeqKG8GkarpBJJxbOJR+GAPPPlbK/sSTdCY1R966Eo8d3Szj61aKBkmHDHbH9t3KkjCTO0wgpV15bA==
< content-length: 54
content-length: 54
< date: Sat, 04 Feb 2023 20:00:33 GMT
date: Sat, 04 Feb 2023 20:00:33 GMT
< priority: u=3,i
priority: u=3,i
< alt-svc: h3=":443"; ma=86400
alt-svc: h3=":443"; ma=86400

< 
* Connection #0 to host graph.facebook.com left intact

The only thing that stands out to me is the explicit "HTTP/2"...

Here's my current code, token redacted:

/*   Build with: g++ gitExample.cpp -o gitExample -ggdb -std=c++14 `curl-config --libs` -lrestc-cpp -lz -lssl -lcrypto -lpthread -lboost_system -lboost_program_options -lboost_filesystem -lboost_date_time -lboost_context -lboost_coroutine -lboost_chrono -lboost_log -lboost_thread -lboost_log_setup -lboost_regex -lboost_atomic -lpthread 
*/

#include <iostream>
#include "restc-cpp/restc-cpp.h"
#include "restc-cpp/RequestBuilder.h" 

using namespace std;
using namespace restc_cpp;


void DoSomethingInteresting(Context& ctx) {

    try {
        auto reply = RequestBuilder(ctx)
        .Get("https://graph.facebook.com/v15.0/me?access_token=[token]")

        // Add some headers for good taste
        .Header("content-type","application/json; charset=UTF-8")
        .Header("Host", "graph.facebook.com")
        .Header("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0")
        .Header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
        .Header("Accept-Language", "en-CA,en-US;q=0.7,en;q=0.3")
        //.Header("Accept-Encoding", "gzip, deflate, br")
        .Header("DNT", "1")
        .Header("Connection", "keep-alive")
        .Header("Upgrade-Insecure-Requests", "1")
        .Header("Sec-Fetch-Dest", "document")
        .Header("Sec-Fetch-Mode", "navigate")
        .Header("Sec-Fetch-Site", "none")
        .Header("Sec-Fetch-User", "?1")
        .Header("TE", "trailers")

        // Send the request
        .Execute();

        // Asynchronously fetch the entire data-set and return it as a string.
        auto json = reply->GetBodyAsString();

        // Just dump the data.
        cout << "Received data: " << json << endl;
        
    } catch (const exception& ex) {
        clog << "Caught exception: " << ex.what() << endl;
    }
}

int main() {

    // Create a REST client
    auto RESTClient = RestClient::Create();
    
    // Create a co-routine that runs in a thread
    RESTClient->Process( DoSomethingInteresting );

    // Wait for the thread to finish and return.
    RESTClient->CloseWhenReady(true);
    
    return 0;
}

Any help appreciated.

@bbogart
Copy link
Author

bbogart commented Feb 10, 2023

For clarification, if you don't include the token, the API should still return an error as JSON; the issue is somewhere in how RESTC-cpp is sending requests that is different than curl or a browser.

Has anyone used RESTC with the Meta Graph API?

@jgaa
Copy link
Owner

jgaa commented Feb 11, 2023

I have no experience with this particular API.

What I would do myself to troubleshoot it is:

  1. Check with curl, forcing it to use http 1.1. --http1.1. There are some API's that require or expect newer HTTP version to work.
  2. Use Wireshark to sniff the curl and restc traffic, and see what differences there are. If it's not the HTTP version, it's probably something with the headers. It could be a subtle difference.

@bbogart bbogart changed the title Simple Graph API example "400 Bad Request" using simple Graph API call over https Feb 14, 2023
@bbogart
Copy link
Author

bbogart commented Feb 14, 2023

1. Check with curl, forcing it to use http 1.1. `--http1.1`. There are some API's that require or expect newer HTTP version to work.

Confirmed with Lynx and Curl that the API is happy with http1.0 and 1.1

2. Use Wireshark to sniff the curl and restc traffic, and see what differences there are. If it's not the HTTP version, it's probably something with the headers. It could be a subtle difference.

Since the URL is https, I've been using the SSLKEYLOGFILE env variable to decode SSL/TLS packets in wireshark. As restc-cpp does not use libcurl, it seems sniffing restc-cpp decrypted packets is not possible.

With wireshark, I've looked at the headers for chromium, firefox (http2), lynx (http1.0) and curl (http1.1) and nothing looks special (all four get the proper json response). I put the same headers into my code above as used in lynx, and the Bad Request result persists.

I've included redacted info about the Get requests from wireshark for lynx and curl below for reference. Since these all use libcurl and work, and restc uses openssl or libressl, could that be the issue? Using openssl 1.1.1f-1ubuntu2.16 and confirm my restc-cpp is built with it:

OPENSSL_SSL_LIBRARY /usr/lib/x86_64-linux-gnu/libssl.so

Here is the lynx and curl output from wireshark for working calls to the API:

Hypertext Transfer Protocol
    GET /v15.0/me?access_token=[redacted] HTTP/1.0\r\n
        [ [truncated]Expert Info (Chat/Sequence): GET /v15.0/me?access_token=[redacted]]
            [GET /v15.0/me?access_token=[redacted] HTTP/1.0\r\n]
            [Severity level: Chat]
            [Group: Sequence]
        Request Method: GET
        Request URI: /v15.0/me?access_token=[redacted]
            Request URI Path: /v15.0/me
            Request URI Query: access_token=[redacted]
                Request URI Query Parameter: access_token=[redacted]
        Request Version: HTTP/1.0
    Host: graph.facebook.com\r\n
    Accept: text/html, text/plain, text/sgml, text/css, */*;q=0.01\r\n
    Accept-Encoding: gzip, compress, bzip2\r\n
    Accept-Language: en\r\n
    User-Agent: Lynx/2.8.9dev.16 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/3.5.17\r\n
    \r\n
    [Full request URI [truncated]: https://graph.facebook.com/v15.0/me?access_token=[redacted]]
    [HTTP request 1/1]
    [Response in frame: 35255]
Hypertext Transfer Protocol
    GET /v15.0/me?access_token=[redacted] HTTP/1.1\r\n
        [ [truncated]Expert Info (Chat/Sequence): GET /v15.0/me?access_token=[redacted]]
            [GET /v15.0/me?access_token=[redacted] HTTP/1.1\r\n]
            [Severity level: Chat]
            [Group: Sequence]
        Request Method: GET
        Request URI: /v15.0/me?access_token=[redacted]
            Request URI Path: /v15.0/me
            Request URI Query: access_token=[redacted]
                Request URI Query Parameter: access_token=[redacted]
        Request Version: HTTP/1.1
    Host: graph.facebook.com\r\n
    User-Agent: curl/7.58.0\r\n
    Accept: */*\r\n
    \r\n
    [Full request URI [truncated]: https://graph.facebook.com/v15.0/me?access_token=[redacted]]
    [HTTP request 1/1]

@jgaa
Copy link
Owner

jgaa commented Feb 14, 2023

Looking closer, I found 2 issues.

  1. You should not add the query argument(s) to the URL, but rather use the .Argument() method in RequestBuilder. That makes sure that the escaping of special characters in the URL is correct.
  2. If you want to see the payload after a failed request, you must set Request::Properties::throwOnHttpError to false. (Yes, I know, that's not intuitive - but some old code, not maintained by me, would break if I changed that).

I have created a branch with a new example, graphapi that addresses these two issues. I don't have a valid access_token, so I have only tested with the error-condition.

$ ./graphapi 
2023-02-14 15:52:15.001 EET INFO 76588 Logging on level info. Boost version is 1_74
Calling: https://graph.facebook.com/v15.0/me
HTTP response : 400 Bad Request
Received data: {"error":{"message":"Invalid OAuth access token - Cannot parse access token","type":"OAuthException","code":190,"fbtrace_id":"AT_aJNPMJRBjRnztzks8mM1"}}

@bbogart
Copy link
Author

bbogart commented Feb 14, 2023

Success! Indeed the issue was missing the .Argument() to pass the token. Note sure how I missed that! No headers appear to to be needed.

Note that in the branch linked, there seems to be a typo on line 24 (a superfluous 's'):

const auto url = "https://graph.facebook.com/v15.0/me"s;

Looks like this would be a good basis for a very simple example.

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants