LiQuick Ответов: 1

Как построить веб - "прокси" для API


Дорогой читатель,

Для проекта мне нужно частично выставить сторонний API на всеобщее обозрение. Для этого я думал, что могу легко получить входящий запрос, перенаправить его в API, получить ответ обратно и доставить ответ клиенту. Довольно простая концепция в теории, но почему-то проблематичная. Частично прокси работает, но когда я получаю доступ к прокси через браузер, некоторые данные выглядят одинаково, а другие-по-другому (json вместо XML), а некоторые я даже не могу отобразить (gzip chunked).

Пожалуйста, помогите (звучит действительно отчаянно, и я),
Реми Самульски

Что я уже пробовал:

Я построил веб-форму, которая примет запрос, создаст новый HttpRequestMessage и отправит его через HttpClient в API. Я получаю HttpResponseMessage, вытаскиваю содержимое (здесь часть GZIP идет неправильно) и отправляю его обратно через HttpResponse. Приведенный ниже код-это все, что необходимо для репликации с помощью онлайн-API OData. Следующие URL-адреса, похоже, работают аналогично в браузере через прокси-сервер или напрямую (в хорошем XML-представлении):
/Proxy2/
/Proxy2/$метаданные

Но это дает разные результаты:
/Proxy2/Customers (дает JSON, но непосредственно дает XML в обычном ASCII, без представления XML)
/Proxy2/Employees (дает ошибку, но непосредственно дает XML в обычном ASCII)

public class Global : HttpApplication
{
    void Application_Start(object sender, EventArgs e)
    {
        // Code that runs on application startup
        GlobalConfiguration.Configure(WebApiConfig.Register);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        if ((Request.RawUrl.ToLower() + "/").StartsWith("/proxy2/") == true)
        {
            Context.RewritePath("~/_Proxy2.aspx", false);
        }
    }
}

public partial class _Proxy2 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string url = Request.RawUrl.Replace("/Proxy2", "").TrimStart('/');

        System.Net.Http.HttpRequestMessage httpRequestMessage =
            new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, "https://services.odata.org/V4/Northwind/Northwind.svc/" + url);
        Business.Http.AddHeaders(Request, httpRequestMessage);

        System.Net.Http.HttpResponseMessage httpResponseMessage;
        System.IO.Stream contentStream;
        using (System.Net.Http.HttpClient httpClient = new System.Net.Http.HttpClient())
        {
            httpResponseMessage = httpClient.SendAsync(httpRequestMessage).Result;
            contentStream = httpResponseMessage.Content.ReadAsStreamAsync().Result;
        }

        Response.Clear();
        Response.ClearHeaders();
        Business.Http.AddHeaders(Response, httpResponseMessage);

        contentStream.CopyTo(Response.OutputStream);
        Response.End();
    }
}

public class Http
{
    public static void AddHeaders(HttpRequest httpRequest, System.Net.Http.HttpRequestMessage httpRequestMessage)
    {
        foreach (string key in httpRequest.Headers.Keys)
        {
            string values = httpRequest.Headers[key];
            System.Diagnostics.Debug.WriteLine("Request " + key + " : " + values);
            switch (key)
            {
                case "Host":
                    //Removed due to SSL problems
                    break;
                default:
                    bool found = false;
                    IEnumerable<string> valuesCurrent;
                    if (httpRequestMessage.Headers.TryGetValues(key, out valuesCurrent) == true)
                    {
                        foreach (var item in valuesCurrent)
                        {
                            if (item.ToLower() == values.ToLower())
                            {
                                found = true;
                                break;
                            }
                        }
                    }
                    if (found == false)
                    {
                        httpRequestMessage.Headers.Add(key, values);
                    }
                    break;
            }
        }
        System.Diagnostics.Debug.WriteLine("");
        Business.Reflection.DebugShowAllProperties(httpRequest);
        System.Diagnostics.Debug.WriteLine("");
        Business.Reflection.DebugShowAllProperties(httpRequestMessage);
        System.Diagnostics.Debug.WriteLine("");
    }

    public static void AddHeaders(HttpResponse httpResponse, System.Net.Http.HttpResponseMessage httpResponseMessage)
    {
        httpResponse.CacheControl = httpResponseMessage.Headers.CacheControl.ToString();

        foreach (KeyValuePair<string, System.Collections.Generic.IEnumerable<string>> item in httpResponseMessage.Headers)
        {
            AddHeaders(httpResponse, item);
        }
        System.Diagnostics.Debug.WriteLine("");
        foreach (KeyValuePair<string, System.Collections.Generic.IEnumerable<string>> item in httpResponseMessage.Content.Headers)
        {
            AddHeaders(httpResponse, item);
        }

        System.Diagnostics.Debug.WriteLine("");
        Business.Reflection.DebugShowAllProperties(httpResponse);
        System.Diagnostics.Debug.WriteLine("");
        Business.Reflection.DebugShowAllProperties(httpResponseMessage);
        System.Diagnostics.Debug.WriteLine("");
    }

    private static void AddHeaders(HttpResponse httpResponse, KeyValuePair<string, System.Collections.Generic.IEnumerable<string>> keyValuePair)
    {
        string key = keyValuePair.Key;
        string values = "";
        foreach (string stringValue in keyValuePair.Value)
        {
            if (values.Contains("," + stringValue) == false)
            {
                values += "," + stringValue;
            }
        }
        values = values.Trim(',');
        System.Diagnostics.Debug.WriteLine("Response " + key + " : " + values);
        switch (key)
        {
            case "Content-Type":
                httpResponse.ContentType = values;
                break;
            default:
                httpResponse.Headers.Add(key, values);
                break;
        }
    }
}

public class Reflection
{
    public static void DebugShowAllProperties(object obj)
    {
        var properties = GetProperties(obj);

        System.Diagnostics.Debug.WriteLine(obj.GetType().FullName);

        foreach (var p in properties)
        {
            string name = p.Name;
            string value = "NULL";
            try
            {
                object valueObject = p.GetValue(obj, null);

                if (valueObject != null)
                {
                    value = valueObject.ToString();
                }
            }
            catch
            {

            }
            System.Diagnostics.Debug.WriteLine(name + " : " + value);
        }

    }

    private static System.Reflection.PropertyInfo[] GetProperties(object obj)
    {
        return obj.GetType().GetProperties();
    }
}

1 Ответов

Рейтинг:
1

Derek Viljoen

Запустите Postman против вашего API сервиса и посмотрите, что он вам дает. Попробуйте точно воспроизвести то, что генерирует ваш прокси-сервер (обратите особое внимание на заголовки HTTP). Вы можете проверить это в Fiddler. Отправляете ли вы контент-тип, и если да, то на что он настроен? Отправляете ли вы заголовок "принять" в запросе? Вы должны указать, что вы хотите обратно (Json/xml). Проверьте это с почтальоном.


LiQuick

Привет Дерек,
Спасибо за ваши предложения. Когда я посещаю свой прокси-сервер, Chrome будет отправлять следующие заголовки (я удалил несколько, чтобы быть кратким). Так что да, браузер будет отправлять несколько возможных типов контента, которые он может обрабатывать.
Просьба принять : текст/HTML,применение/с xhtml+xml,в приложение/XML;Q в=0.9,изображений/файлов WebP,изображения/ранп,*/*;Q в=0.8
Запрос Accept-кодировка : gzip, deflate, br
Запрос Accept-Language : nl-NL,nl;q=0.9,en;q=0.8,en-US;q=0.7
Запрос User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, как Gecko) Chrome/72.0.3626.109 Safari/537.36

Я скопировал эти заголовки в HttpRequestMessage, чтобы он имитировал запрос браузера:
Системы.Нет.Протоколу HTTP.Объектами httprequestmessage
Версия : 1.1
Содержание : NULL
метод get
RequestUri : https://services.odata.org/V4/Northwind/Northwind.svc/Customers
Заголовки : Соединение: Keep-Alive
Принимаем: текст/HTML, применение/с xhtml+xml, в приложение/XML; Q в=0.9, изображений/файлов WebP, изображения/ранп, */*; Q в=0.8
Accept-кодировка: gzip, deflate, br
Accept-Language: nl-NL, nl; q=0.9, en; q=0.8, en-US; q=0.7
Агент пользователя: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, как Gecko) Chrome/72.0.3626.109 Safari/537.36
Обновления Безопасности-Запросов: 1

Поэтому, с моей точки зрения, не должно быть никакой разницы между прямым запросом к сервису OData и через мой HttpClient. Я попытаюсь вынюхать, как вы рекомендовали, разницу между моим прокси и веб-браузером.

Тнх

LiQuick

ПС. Сегодня на работе (вчера я был дома) часть данных, которые отличались между прямым запросом и прокси-запросом, одинаковы. Это делает меня еще более напряженным.