Асинхронный NancyFX с HttpClient - GetAsync для fbcdn возвращает 403 запрещено?

ПРИМЕЧАНИЕ. Я немного изменил этот вопрос, чтобы лучше указать на проблему. Комментарии ниже больше не отражают этот вопрос.

Я пытаюсь получить это изображение с fbcdn:

https ://scontent.xx.fbcdn.net/v/t1.0-1/c15.0.50.50/p50x50/10354686_10150004552801856_220367501106153455_n.jpg?oh=6c801f82cd5a32fd6e5a4258ce0oe2a4294a

Браузер это прекрасно понимает. Вот мой код:

public class ReverseProxyController : NancyModule
{
    public ReverseProxyController()
    {
        Get["/", true] = async (parameters, ct) =>
        {
            var result = await GetResult(parameters, ct);
            return result;
        };
    }

    private async Task<Response> GetResult(dynamic parameters, CancellationToken ct)
    {
        var client = new HttpClient();
        string url = Request.Query["url"].Value.ToString();
        if (url == null) return null;

        client.DefaultRequestHeaders.Add("Access-Control-Allow-Origin", "*");
        client.DefaultRequestHeaders.Add("User-Agent",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
        client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
        client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
        client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, sdch, br");
        client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.8,ru;q=0.6");
        var response = await client.GetAsync(url, ct);

        ct.ThrowIfCancellationRequested();


        switch (response.StatusCode)
        {
            case HttpStatusCode.OK:
                var stream = await response.Content.ReadAsStreamAsync();

                return Response.FromStream(stream, response.Content.Headers.ContentType != null
                    ? response.Content.Headers.ContentType.ToString()
                    : "application/octet-stream");

            default:
                return Response.AsText("\nError " + response.StatusCode);
        }
    }
}

Каждый раз получаю ответ 403 Forbidden. Я думал, что добавление заголовков заставит его работать, но не получилось.

Этот код работает для других изображений на других хостах, таких как этот:

https://s-media-cache-ak0.pinimg.com/564x/ff/f0/c9/fff0c988a4516659d4009f60e0694cb6.jpg


person richard    schedule 29.10.2016    source источник
comment
Итак, Response.FromStream и Response.AsText из какой библиотеки? Или что они делают?   -  person Jim    schedule 29.10.2016
comment
хорошо, если об этом не может быть и речи, если я протестирую другой код, он отлично работает здесь с ответом 200   -  person Jim    schedule 29.10.2016
comment
Действительно? С URL-адресом fbcdn?   -  person richard    schedule 29.10.2016
comment
да, сэр, с URL-адресом fbcdn   -  person Jim    schedule 29.10.2016
comment
Хммм. Хорошо, я попробую это. Я не думал, что это будет иметь значение. И это работает для других URL-адресов, но не для fbcdn. Я дам вам знать, что я узнаю.   -  person richard    schedule 29.10.2016
comment
Вы пытались посмотреть исключение или внутреннее исключение, если они доступны? Ваш код просто отлично работает на моем конце.   -  person dotnetstep    schedule 29.10.2016
comment
.net не исключение. Это ответ 403 от сервера fbcdn.   -  person richard    schedule 29.10.2016
comment
Похоже, ваш запрос был заблокирован сервером. рассмотрите возможность использования прокси-IP и изменения заголовков...   -  person Zen    schedule 31.10.2016
comment
Привет, Дзен ... другой плакат вчера сказал, что это сработает, если он сделает запрос вне этого кода Нэнси.   -  person richard    schedule 31.10.2016
comment
@richard, только что написал модульный тест для получения URL-адреса, и он работает для вызовов http, но не работает для https, что, я думаю, связано с сертификатом. Это был простой тест только с URL-адресом. никаких других заголовков. получить ответ 200 OK   -  person Nkosi    schedule 31.10.2016
comment
@Nkosi, это работает для некоторых https. Например, URL-адрес, который у меня есть в моем вопросе на сайте кеша мультимедиа, работает: ="nofollow noreferrer">s-media-cache-ak0.pinimg.com/564x/ff/f0/c9/   -  person richard    schedule 31.10.2016
comment
@richard да, мои модульные тесты тоже это отражают. Итак, теперь я пытаюсь понять, в чем проблема, поскольку мои модульные тесты показывают, что доступ к файлам можно получить с помощью простого getasync на httpclient.   -  person Nkosi    schedule 31.10.2016
comment
Ваш модульный тест успешно получает образ fbcdn? Также ваш модульный тест использует Nancy async?   -  person richard    schedule 31.10.2016
comment
модульный тест получает изображение fbcdn при использовании схемы http вместо https. https выдает ошибку сертификата. простой асинхронный модульный тест с URL-адресом вызова httpclient.   -  person Nkosi    schedule 31.10.2016
comment
Интересно. Что за ошибка сертификата? Что сертификат fbcdn недействителен?   -  person richard    schedule 31.10.2016
comment
К сожалению, когда я повторно запустил его для ошибки https для записи, теперь он работает. странный.   -  person Nkosi    schedule 31.10.2016
comment
Попробуй с Нэнси. Посмотри, может в этом проблема?   -  person richard    schedule 31.10.2016
comment
Здесь отлично работает с примером ссылки, просто сделал это с помощью простого консольного приложения, вы уверены, что дело не только в том, что некоторые ссылки, которые вы используете, недоступны для просмотра без входа в систему?   -  person Henrik Bøgelund Lavstsen    schedule 31.10.2016
comment
Конкретная ссылка у меня не работает.   -  person richard    schedule 31.10.2016


Ответы (2)


Проблема заключается в получении URL-адреса из запроса Нэнси, а не в получении данных с HttpClient.

Я предполагаю, что вы отправляете запрос Нэнси, например:

http://localhost/?url=...

следовательно, для Facebook это будет:

http://localhost/?url=https://scontent.xx.fbcdn.net/v/t1.0-1/c15.0.50.50/p50x50/10354686_10150004552801856_220367501106153455_n.2=oh=6c5o1f82cd8a32f589AAD2F

но для этого URL-адрес string url = Request.Query["url"].Value.ToString(); неполный и отсутствует последняя часть (& oe = 589AAD2F), поэтому сервер отвечает «Запрещено».

введите описание изображения здесь

Вот простое изменение, демонстрирующее проблему:

private async Task<Response> GetResult(dynamic parameters, CancellationToken ct)
{
    var client = new HttpClient();
    var req = Request.Url.ToString();
    var queryStart = req.IndexOf("url=");
    if (queryStart == -1)
        return Nancy.HttpStatusCode.BadRequest;

    var url = req.Substring(queryStart + 4);
    if (string.IsNullOrEmpty(url))
        return Nancy.HttpStatusCode.BadRequest;

    client.DefaultRequestHeaders.Add("Access-Control-Allow-Origin", "*");
    client.DefaultRequestHeaders.Add("User-Agent",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
    client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
    client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
    client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, sdch, br");
    client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.8,ru;q=0.6");
    var response = await client.GetAsync(url, ct);

    ct.ThrowIfCancellationRequested();


    switch (response.StatusCode)
    {
        case System.Net.HttpStatusCode.OK:
            var stream = await response.Content.ReadAsStreamAsync();

            return Response.FromStream(stream, response.Content.Headers.ContentType != null
                ? response.Content.Headers.ContentType.ToString()
                : "application/octet-stream");

        default:
            return Response.AsText("\nError " + response.StatusCode);
    }
}

Решение

На самом деле мы можем закодировать URL-адрес перед его отправкой, и Нэнси автоматически декодирует URL-адрес для нас, поэтому нет необходимости что-либо менять на стороне сервера.

Вот пример ссылки, созданной с использованием HttpUtility.UrlEncode, примененного к

https ://scontent.xx.fbcdn.net/v/t1.0-1/c15.0.50.50/p50x50/10354686_10150004552801856_220367501106153455_n.jpg?oh=6c801f82cd5a32fd6e5a4258ce0oe2a4294a

Результат:

HTTPS% 3a% 2f% 2fscontent.xx.fbcdn.net% 2fv% 2ft1.0-1% 2fc15.0.50.50% 2fp50x50% 2f10354686_10150004552801856_220367501106153455_n.jpg% 3foh% 3d6c801f82cd5a32fd6e5a4258ce00a314% 26oe% 3d589AAD2F

и фактический запрос для этой конкретной ссылки будет:

http://localhost:9876/?url=https%3a%2f%2fscontent.xx.fbcdn.net%2fv%2ft1.0-1%2fc15.0.50.50%2fp50x50%2f10354686_10150004552801856_220367501106_n.jpg%553foh%3d6c801f82cd5a32fd6e5a4258ce00a314%26oe%3d589AAD2F

Альтернативное решение

Я лично предпочитаю POST вместо GET в этой ситуации, так что вот оно:

public class ReverseProxyController : NancyModule
{
    class ProxyRequest
    {
        public string Url { get; set; }
    }

    public ReverseProxyController()
    {
        Post["/", true] = async (parameters, ct) =>
        {
            var result = await GetResult(parameters, ct);
            return result;
        };
    }

    private async Task<Response> GetResult(dynamic parameters, CancellationToken ct)
    {
        var pReq = this.Bind<ProxyRequest>();
        var url = pReq.Url;
        if (string.IsNullOrEmpty(url))
            return null;

        var client = new HttpClient();
        client.DefaultRequestHeaders.Add("Access-Control-Allow-Origin", "*");
        client.DefaultRequestHeaders.Add("User-Agent",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
        client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
        client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
        client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, sdch, br");
        client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.8,ru;q=0.6");
        var response = await client.GetAsync(url, ct);

        ct.ThrowIfCancellationRequested();


        switch (response.StatusCode)
        {
            case System.Net.HttpStatusCode.OK:
                var stream = await response.Content.ReadAsStreamAsync();

                return Response.FromStream(stream, response.Content.Headers.ContentType != null
                    ? response.Content.Headers.ContentType.ToString()
                    : "application/octet-stream");

            default:
                return Response.AsText("\nError " + response.StatusCode);
        }
    }
}
person user3473830    schedule 31.10.2016
comment
Это проблема. Я только что проверил, прежде чем увидел этот ответ, и последний код &oe=589AAD2F отсутствует. - person richard; 31.10.2016
comment
Почему Нэнси усекает этот последний параметр запроса? - person richard; 31.10.2016
comment
Неважно... Нэнси обрезает его из-за того, как Нэнси токенизирует URL-адрес... он дает мне то, что, по ее мнению, является параметром, который мне нужен. Есть ли способ правильно отформатировать маршрут, чтобы получить то, что я хочу? - person richard; 31.10.2016
comment
@richard, вы можете закодировать URL-адрес перед его отправкой, Нэнси автоматически расшифрует его для вас, например. с помощью HttpUtility.UrlEncode() я получил localhost: 9876 / и оригинальный код работает нормально, я буду обновлять ответ, чтобы отразить это - person user3473830; 31.10.2016
comment
Можете ли вы также поставить оригинальное решение с подстрокой? Я не могу контролировать, что отправляется Нэнси, поэтому я не могу убедиться, что это закодировано... - person richard; 01.11.2016

в качестве консольного приложения ваш код работает нормально

   static void Main(string[] args)
    {
        GetStuff();
        Console.ReadLine();
    }

    private static async void GetStuff()
    {
        CancellationToken ct = new CancellationToken();
        var client = new HttpClient();
        //this is only a Simple Change to demonstrate the problem, and should not be considered as a proper solution

        string url = "https://scontent.xx.fbcdn.net/v/t1.0-1/c15.0.50.50/p50x50/10354686_10150004552801856_220367501106153455_n.jpg?oh=6c801f82cd5a32fd6e5a4258ce00a314&oe=589AAD2F";
        if (url == null) return;

        client.DefaultRequestHeaders.Add("Access-Control-Allow-Origin", "*");
        client.DefaultRequestHeaders.Add("User-Agent",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
        client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
        client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
        client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, sdch, br");
        client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.8,ru;q=0.6");
        var response = await client.GetAsync(url, ct);

        ct.ThrowIfCancellationRequested();


        switch (response.StatusCode)
        {
            case System.Net.HttpStatusCode.OK:
                var stream = await response.Content.ReadAsStreamAsync();


                break;
            default:
                break;
        }
    }

я получаю ответное сообщение OK 200. Это другие изображения, которые вы используете, которым может потребоваться информация для входа?

person Henrik Bøgelund Lavstsen    schedule 31.10.2016