从iOS模拟器和控制台应用程序接收具有相同HTTP请求的不同响应

时间:2018-04-04 02:24:19

标签: ios xamarin xamarin.forms xamarin.ios .net-core

我目前正在使用Xamarin Forms开发应用。我的开发环境是Mac OS,Visual Studio和C#。我正在开发的应用程序将与Web服务连接。该服务是FamilySearch,这是一个家谱网站。

我一直在编写一些代码来发送请求并处理来自其服务器的响应。我写了一个我认为格式正确的请求,但收到的回复表明“未经授权”。

然后我决定将我的代码运行在.NET Core控制台应用程序中,以避免使用Xamarin Forms的开销。我发送了相同的确切请求,字节为字节。这样做时,我得到了一个成功的响应(状态代码200)。

因此,我有2个相同的HTTP请求,一个是从Xamarin Forms应用程序中的iOS模拟器发送的,另一个是从.NET Core应用程序中的控制台发送的。他们从服务器接收不同的响应。知道为什么会这样吗?

这是一些代码,因此您可以了解我在做什么。首先,我设置了一些HttpClient对象(一个针对他们的身份验证服务器,另一个针对服务器处理其他调用):

HttpClient _identity_host = new HttpClient();
HttpClient _platform_host = new HttpClient();
_identity_host.BaseAddress = new System.Uri("https://identint.familysearch.org");
_platform_host.BaseAddress = new System.Uri("https://api-integ.familysearch.org");
_identity_host.DefaultRequestHeaders.Accept.Add(
    new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
_platform_host.DefaultRequestHeaders.Accept.Add(
    new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

设置HttpClient对象后,我调用此net函数登录到我的用户帐户。 此功能在Xamarin Forms应用程序和.NET Core控制台应用程序中都很成功

public async void AttemptLogin(string username, string password)
{
    //Form the web request
    Dictionary<string, string> login_content_pairs = new Dictionary<string, string>()
    {
        { "password", _password },
        { "grant_type", "password" },
        { "client_id", _application_id },
        { "username", _username }
    };

    string login_content = this.ToQueryString(login_content_pairs, false);
    StringContent content = new StringContent(login_content, Encoding.UTF8, "application/x-www-form-urlencoded");
    var result = await _identity_host.PostAsync("/cis-web/oauth2/v3/token", content);
    if (result.IsSuccessStatusCode)
    {
        var token_json = await result.Content.ReadAsStringAsync();
        JObject parsed_json = JObject.Parse(token_json);

        if (parsed_json.ContainsKey("access_token"))
        {
            _access_token = (string)parsed_json["access_token"];
        }
        else if (parsed_json.ContainsKey("token"))
        {
            _access_token = (string)parsed_json["token"];
        }

        string k = (string)parsed_json["token"];

        //Set the authorization header on the platform host object
        _platform_host.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _access_token);
        _successful_login = true;
    }
}

最后,在登录尝试完成后,我然后使用我的授权令牌从他们的服务器请求一些东西:

public async void GetCurrentPerson()
{
    var result = await _platform_host.GetAsync("/platform/tree/current-person");
    if (result.IsSuccessStatusCode)
    {
        var token_json = await result.Content.ReadAsStringAsync();
        JObject parsed_json = JObject.Parse(token_json);
    }
}

上面的GET请求是返回两个不同响应的请求 - 取决于我是使用带有Xamarin Forms的iOS模拟器还是使用.NET Core控制台应用程序。

以下是来自Visual Studio调试器的GET请求的ToString():

{
    Method: GET,
    RequestUri: 'https://api-integ.familysearch.org/platform/tree/persons/L5FY-BQW',
    Version: 1.1,
    Content: <null>,
    Headers:
    {   
        Accept: application/json
        Authorization: Bearer MY_AUTHORIZATION_TOKEN
    }
}

在控制台应用中,我收到了以下回复:

{
    StatusCode: 200,
    ReasonPhrase: 'OK',
    Version: 1.1,
    Content: System.Net.Http.NoWriteNoSeekStreamContent,
    Headers:
    {
        Cache-Control: no-transform, must-revalidate, max-age=0
        Date: Wed, 04 Apr 2018 01:40:13 GMT
        ETag: "137412002955880000"
        Server: Apache-Coyote/1.1
        Vary: Accept
        Vary: Accept-Language
        Vary: Accept-Encoding
        Vary: Expect
        Vary: Accept-Encoding
        Warning: 199 FamilySearch Best Practice Violation: Should specify versioned media type in Accept header, e.g. one of [ "application/x-fs-v1+xml", "application/x-fs-v1+json", "application/atom+xml", "application/x-gedcomx-atom+json", "application/x-gedcomx-v1+xml", "application/x-gedcomx-v1+json" ].
        X-PROCESSING-TIME: 184
        Connection: keep-alive
        Allow: OPTIONS
        Allow: HEAD
        Allow: GET
        Allow: POST
        Allow: DELETE
        Content-Location: /tree/persons/L5FY-BQW
        Content-Type: application/json
        Last-Modified: Sat, 24 Mar 2018 16:04:55 GMT
        Content-Length: 6479
    }
}

虽然来自Xamarin Forms应用程序的相同请求使用iOS模拟器会产生以下响应:

{
    StatusCode: 401, 
    ReasonPhrase: 'Unauthorized', 
    Version: 1.1, 
    Content: System.Net.Http.StreamContent, 
    Headers: 
    { 
        Cache-Control: no-cache, no-store, no-transform, must-revalidate, max-age=0 
        Date: Wed, 04 Apr 2018 01:45:02 GMT 
        Link: <https://integration.famil...
    }
}

401 Unauthorized响应的内容如下:

{
    "errors" : [ {
        "code" : 401,
        "message" : "Unable to  read tf person.",
        "stacktrace" : "GET http://tf.integ.us-east-1.dev.fslocal.org/tf/person/L5FY-BQW?oneHops=none returned a response status of 401 Unauthorized: { "401" : "Unauthorized" }"
    } ]
}

非常感谢任何有助于理解出错的帮助。谢谢!

1 个答案:

答案 0 :(得分:0)

想出来!我终于能够让Charles Proxy工作,这样我就可以窥探来自iOS模拟器的HTTP SSL流量。事实证明,我的URL请求最初导致重定向,当它尝试遵循重定向时,授权标头在我不知情的情况下被剥离了请求。

以下Stack Overflow问题帮我解决了问题:Authorization header is lost on redirect

我的代码现在看起来像这样:

public async Task<int> GetCurrentPerson()
{
    var result = await _platform_host.GetAsync(_current_person_uri);

    //Check to see if the result was a "401 Unauthorized"
    if (result.StatusCode == HttpStatusCode.Unauthorized)
    {
        //If so, the "Authorization" header was likely stripped after a redirect
        //We will simply follow the redirect URL by calling GetAsync, and the
        //authorization header should be placed back on.

        //contains the final location after following the redirect.
        var finalRequestUri = result.RequestMessage.RequestUri; 

        //detect that a redirect actually did occur.
        if (finalRequestUri != _current_person_uri) 
        {
            // check that we can trust the host we were redirected to.
            if (IsHostTrusted(finalRequestUri)) 
            {
                // Reissue the request. The DefaultRequestHeaders configured 
                //on the client will be used, so we don't have to set them again.
                result = await _platform_host.GetAsync(finalRequestUri); 
            }
        }    
    }

    //Now check to see if the status is successful
    if (result.IsSuccessStatusCode)
    {
        ... handle JSON that was returned in response ...
    }

    ... rest of function ...
}

private bool IsHostTrusted(Uri uri)
{
    return (uri.Host == _host_address);
}