اخبار، مطالب و رویدادهای مرتبط با توسعه نرم افزار رادکام

HTTP Message Handlers ها و HttpClient Message Handlers ها در Web API

message handler در حقیقت یک کلاس است که درخواست های Http را دریافت کرده و پاسخ های Http را برمی گرداند. کلاس های message handler از کلاس انتزاعی HttpMessageHanler مشتق می شوند.
معمولا یک سری از message handler ها به هم پیوسته کار می کنند. handler ابتدایی درخواست Http را دریافت می کند، پردازش های لازم و مورد نیاز را روی آن انجام می دهد. درخواست http را تحویل handler بعدی می دهد. در برخی موارد جواب ایجاد شده و به سمت ابتدای زنجیر بالا می رودو به این الگو delegating handler گفته می شود.

delegating handler

Message Handler های سمت سرور

در سمت سرور  Web API چند message handler از پیش ساخته شده درون خود دارد:
  • HttpServer که درخواست را از هاست دریافت می کند.
  • HttpRoutingDispatcher که درخواست را بر اساس مسیر یابی مشخص شده توزیع می کند.
  • HttpControllerDispatcher درخواست را به کنترلر Web API ارسال می کند.
ما می توانیم message handler خودمان را به چرخه Web API اضافه کنیم. Message Handler ها برای زمانی که نگران درخواست های متقابل هستیم بسیار مفید واقع می شوند. یعنی زمانی که در سطح Http Message ها (به جای Controller Action  ها) عمل می کنند.
به عنوان مثال یک Message Handler باید:
  • هدرهای درخواست را خوانده و ویرایش می کند.
  • هدر پاسخ را به مجموعه پاسخ ها اضافه می کند.
  • قبل از اینکه درخواست ها به کنترلر برسند آنها را اعتبار سنجی می کند.
دیاگرام زیر دو هندلر  سفارشی را نمایش می دهد که وارد چرخه Web API شده اند:

Message Handler

Message Handler های سفارشی

برای ایجاد یک message handler سفارشی باید کلاس مربوطه را از System.Net.Http.DelegatingHandler مشتق کنید و متد SendAsync را  به روش خودتان باز نویسی کنید. این متد ساختار زیر را دارد:

Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);

این متد HttpRequestMessage را به عنوان ورودی دریافت کرده و به طور آسنکرون HttpResponseMessage را برمی گرداند. پیاده سازی معمول این متد چنین است:
  • message مربوط به درخواست را پردازش می کند.
  • متد base.SendAsync را برای ارسال درخواست به  handler داخلی فراخوانی می کند.
  • handler داخلی message پاسخ را به صورت آسنکرون بر می گرداند.
  • پاسخ را پردازش کرده و آن را به متد فراخواننده آن بر می گرداند.
کد زیر یک مثال ساده از پیاده سازی این متد است:

public class MessageHandler1 : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("Process request");
        // Call the inner handler.
        var response = await base.SendAsync(request, cancellationToken);
        Debug.WriteLine("Process response");
        return response;
    }
}

فراخوانی base.SendAsync به صورت آسنکرون است، اگر هندلر بعد از فراخوانی این متد عملکرد درست نداشته باشد، بهتر است از عبارت await  برای استفاده از آن بهره بگیرید. مانند مثال زیر:
هندلر جایگزین همچنین می تواند هندلر داخلی را رد کرده و خود مستقیما پاسخ را ایجاد کند.

public class MessageHandler2 : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Create the response.
        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent("Hello!")
        };

        // Note: TaskCompletionSource creates a task that does not contain a delegate.
        var tsc = new TaskCompletionSource<HttpResponseMessage>();
        tsc.SetResult(response); // Also sets the task state to "RanToCompletion"
        return tsc.Task;
    }
}

اگر یک هندلر جایگزین، پاسخ را بدون فراخوانی base.SendAsync ایجاد کند، درخواست بقیه مسیر مربوطه را کنار می گذارد. این کار می تواند برای هندلری که درخواست را اعتبار سنجی می کند مفید باشد ( یعنی زمانی که می خواهیم پاسخی حاوی خطا ایجاد کنیم ).

MessageHandlers

اضافه کردن یک هندلر به چرخه Web Api

 برای اضافه کردن یک Message Handler در سمت سرور، هندلر مربوطه را به مجموعه HttpConfiguration.MessageHandlers اضافه کنید. اگر قالب پروژه دات نتی که استفاده می کنید ASP.Net MVC 4 Web Application است، این کار را می توانید داخل کلاس WebApiConfig انجام دهید:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new MessageHandler1());
        config.MessageHandlers.Add(new MessageHandler2());

        // Other code not shown...
    }
}

Message Handler ها به همان ترتیبی که در مجموعه MessageHandler ها قرار گرفته اند فراخوانی و استفاده خواهند شد. از آنجا که آنها تو در تو هستند، پبام پاسخ در جهت مخالف حرکت می کند. به همبن دلیل است که آخرین هندلر مجموعه اولین دریافت کننده پیام پاسخ خواهد بود.
توجه داشته باشید که نیازی نیست تا هندلر های داخلی را هم ست کنید. فریموورک Web API به صورت خودکار message handler ها را متصل خواهد کرد.
اگر برای هاست کردن Web API از Self Hosting استفاده می کنید، یک نمونه از کلاس HttpSelfHostConfiguration ایجاد کرده و مجموعه MessageHandlers را به آن اضافه کنید.
بمانند مثال زیر:

var config = new HttpSelfHostConfiguration("http://localhost");
config.MessageHandlers.Add(new MessageHandler1());
config.MessageHandlers.Add(new MessageHandler2());

حال نگاهی به چند نمونه از message handler ها می اندازیم:


مثال: X-HTTP-Method-Override

X-HTTP-Method-Override یک هدر غیر استاندارد HTTP است. این هدر ببرای کلاینت هایی طراحی شده است که امکان ارسال برخی از انواع درخواست های HTTP را ندارند، مانند درخواست های PUT و یا DELETE. در عوض کلاینت یک درخواست POST ارسال کرده و هدر X-HTTP-Method-Override را برای متد مورد نظر خود ست می کند. به مثال زیر دقت کنید. این یک message handler است که از  X-HTTP-Method-Override پشتیبانی می کند.

public class MethodOverrideHandler : DelegatingHandler
{
    readonly string[] _methods = { "DELETE", "HEAD", "PUT" };
    const string _header = "X-HTTP-Method-Override";

    protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Check for HTTP POST with the X-HTTP-Method-Override header.
        if (request.Method == HttpMethod.Post && request.Headers.Contains(_header))
        {
            // Check if the header value is in our methods list.
            var method = request.Headers.GetValues(_header).FirstOrDefault();
            if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
            {
                // Change the request method.
                request.Method = new HttpMethod(method);
            }
        }
        return base.SendAsync(request, cancellationToken);
    }
}

 در متد SendAsync هندلر بررسی می کند ببیند نوع درخواست دریافت شده POST است و آیا شامل هدر X-HTTP-Method-Override می باشد. اگر چنین باشد، مقدار هدر را اعتبارسنجی کرده و متد درخواست را تغییر می دهد.در انتها ، هندلر متد base.SendAsync را فراخوانی می کند و message را به هندلر بعدی ارجاع می دهد.
وقتی درخواست به کلاس  HttpControllerDispatcher می رسد، این کلاس درخواست را به سمت مسیر مورد نظر آن هدایت می کند ، بر اساس  متد بروزرسانی شده درخواست.


مثال: اضافه کردن یک هدر پاسخ سفارشی

در زیر یک Message Handler ای را می بینیم که یک هدر سفارشی به کلیه پیام های پاسخ اضافه می کند.

// .Net 4.5
public class CustomHeaderHandler : DelegatingHandler
{
    async protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
        response.Headers.Add("X-Custom-Header", "This is my custom header.");
        return response;
    }
}

ابتدا هندلر متد base.SendAsync را فراخوانی می کند، تا درخواست را به هدر پیام داخلی هدایت کند. هندلر داخلی، پیام پاسخ را بر می گرداند، و  این کار را به صورت آسنکرون  با استفاده از شیء Task<T> انجام می دهد. پیام پاسخ زمانی در دسترس قرار می گیرد که کار متد base.SendAsync به صورت آسنکرون تمام شود.
مثال زیر از کلمه کلیدی await استفاده می کند تا بتواند ادامه کار را به صورت آسنکرون بعد از اتمام کار SendAsync انجام دهد. اگر شما از نسخه 4.0 فریموورک دات نت استفاده می کنید از دستور Task<t>.ContinueWith بهره بگیرید.

public class CustomHeaderHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken).ContinueWith(
            (task) =>
            {
                HttpResponseMessage response = task.Result;
                response.Headers.Add("X-Custom-Header", "This is my custom header.");
                return response;
            }
        );
    }
}


مثال: چک کردن API Key

برخی از وب سرویس ها از مشتریانی که از آن سرویس ها استفاده می کنند درخواست می کنند که یک کلید خاصی (API Key) را همراه با درخواست خود به سمت سرویس ارسال کنند. مثال پیش رو نشان می دهد که چگونه یک message handler درخواست های رسیده را بررسی می کند تا ببیند همراه با درخواست، یک API Key معتبر ارسال شده است یا نه.

public class ApiKeyHandler : DelegatingHandler
{
    public string Key { get; set; }

    public ApiKeyHandler(string key)
    {
        this.Key = key;
    }

    protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (!ValidateKey(request))
        {
            var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
            var tsc = new TaskCompletionSource<HttpResponseMessage>();
            tsc.SetResult(response);
            return tsc.Task;
        }
        return base.SendAsync(request, cancellationToken);
    }

    private bool ValidateKey(HttpRequestMessage message)
    {
        var query = message.RequestUri.ParseQueryString();
        string key = query["key"];
        return (key == Key);
    }
}

این هندلر در query string های موجود در URI به دنبال API Key می گردد.(در این مثال ما فرض را بر این گرفته ایم که کلید مورد نظر ما یک رشته ثابت است. در دنیای واقعی قطعا برای پیاده سازی این بخش از ساختار و اعتبار سنجی پیچیده تری باید استفاده کرد). اگر Query String حاوی کلید مورد نظر ما باشد، هندلر ما درخواست را به سمت یک هندلر داخلی هدایت می کند.
ولی اگر درخواست ما شامل کلید معتبر نباشد. هندلر یک پیام پاسخ با وضعیت 403 (ممنوع) ایجاد می کند. در این حالت هندلر دیگر متد base.SendAsync را فراخوانی نخواهد کرد، بنابراین نه هندلر داخلی و نه کنترلر هیچوقت درخواست ارسال شده را دریافت نخواهند کرد.بنابراین کنترلر مطمئن است که تمام درخواست هایی که به آن می رسند ، همگی یک کلید معتبر دارند.

در نظر داشته باشید که اگر API Key قرار هست برای یک متد از یک کنترلر خاص مورد استفاده قرار گیرد، از Action Filter ها به جای Message Handler ها استفاده کنید. Action Filter ها دقیقا بلافاصله بعد از اینکه مسیریابی آدرس (URI routing) انجام شد فراخوانی می شوند.

پردازنده های پیام مبتنی بر مسیر (Per-Route Message Handlers)

پردازنده های پیامی که در مجموعه HttpConfiguration.MessageHandlers قرار دارند در سطح کل Application اعمال می شوند.  از سوی دیگر شما می توانید یک پردازنده پیام را به یک مسیر مشخص هنگام ایجاد آن مسیر اعمال کنید. مثال زیر منظور ما را نشان می دهد.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "Route1",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        config.Routes.MapHttpRoute(
            name: "Route2",
            routeTemplate: "api2/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional },
            constraints: null,
            handler: new MessageHandler2() // per-route message handler
        );

        config.MessageHandlers.Add(new MessageHandler1()); // global message handler
    }
}

در این مثال اگر درخواست ارسال شده با Route2 تطابق داشته باشد، درخواست به سمت  MessageHandler2 ارسال می شود.  دیاگرام زیر مسیر حرکتی را برای این دو Route نشان می دهد.

Message Handler

در نظر داشته باشید که MessageHandler2 جایگزین HttpControllerDispatcher پیش فرض می شود. در این مثال MessageHandler2 پاسخ را ایجاد می کند . درخواستی که با Route2 تطابق دارد هیچ وقت سمت کنترلر هدایت نخواهد شد. این روش به شما این امکان را خواهد داد تا مکانیسم فعلی پیاده سازی شده برای کنترلر های Web API را با مکانیزم سفارشی خود جایگزین کنید.
از سوی دیگر، پردازنده های پیام مبتنی بر مسیر،  میتوانند به HttpControllerDispatcher هدایت شوند، و سپس به سمت کنترلر مربوطه هدایت شوند. به شکل زیر توجه کنید.

Message Handler2

کد زیر نشان می دهد که چگونه می توان این مسیر یابی را پیکربندی کرد.


// List of delegating handlers.
DelegatingHandler[] handlers = new DelegatingHandler[] {
    new MessageHandler3()
};

// Create a message handler chain with an end-point.
var routeHandlers = HttpClientFactory.CreatePipeline(
    new HttpControllerDispatcher(config), handlers);

config.Routes.MapHttpRoute(
    name: "Route2",   
    routeTemplate: "api2/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional },
    constraints: null,
    handler: routeHandlers
);

حال به بررسی  HttpClient Message Handlers می پردازیم.
همانطور که در ابتدای مطالب گفتیم،  یک پردازنده پیام (message handler) یک کلاس است که درخواست HTTP را دریافت کرده و یک جواب HTTP بر می گرداند.

معمولا، یک سری از پردازنده های پیام زنجیروار به هم متصل هستند. پردازنده اول درخواست HTTP را دریافت کرده و پردازش های لازم و مورد نیاز را روی آن انجام داده و درخواست را تحویل پردازنده بعدی می دهد.  در برخی از مواقع پاسخ ایجاد شده و به بالای زنجیره ارسال می شود. این الگو delegating handler گفته می شود.

Message Handler

در سمت کلاینت کلاس HttpClient از پردازنده پیام برای پردازش درخواست ها استفاده می کند.پردازنده پیشفرض HttpClientHandler است که درخواست را درون شبکه ارسال می کند و پاسخ را از سرور دریافت می کند. شما می توانید یک پردازنده پیام سفارشی ایجاد کرده و وارد مسیر مورد استفاده کلاینت کنید.

Message Handler های سفارشی

در بالاتر توضیحات مورد نیاز در رابطه با پردازنده های پیام سفارشی  ارایه شد، فقط اشاره دیگری به نحوه پیاده سازی متفاوت با مثال بالا ارایه می کنیم. مثال زیر یک پردازنده پیام را نشان می دهد که به درخواست خروجی  یک هدر سفارشی اضافه می کند.

class MessageHandler1 : DelegatingHandler
{
    private int _count = 0;

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        System.Threading.Interlocked.Increment(ref _count);
        request.Headers.Add("X-Custom-Header", _count.ToString());
        return base.SendAsync(request, cancellationToken);
    }
}

همانطور که پیشتر گفته بودیم، فراخوانی  base.SendAsync به صورت آسنکرون انجام می پذیرد. اگر قرار هست پردازنده بعد از فراخوانی این متد، کار دیگری نیز انجام دهد، از کلمه کلیدی await استفاده کنید، تا بعد از کامل شدن متد، اجرای آن را  از سر بگیرد. مثال زیر پردازنده ای را نشان می دهد که کد های خطا را ثبت می کند. ثبت خطا ها به تنهایی مقوله جذابی به نظر نمی رسد، اما مثال زیر نشان می دهد که  چگونه می توان پاسخ را داخل یک پردازنده دریافت کرد.

class LoggingHandler : DelegatingHandler
{
    StreamWriter _writer;

    public LoggingHandler(Stream stream)
    {
        _writer = new StreamWriter(stream);
    }

    protected override async Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        var response = await base.SendAsync(request, cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            _writer.WriteLine("{0}\t{1}\t{2}", request.RequestUri,
            (int)response.StatusCode, response.Headers.Date);
        }
        return response;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _writer.Dispose();
        }
        base.Dispose(disposing);
    }
}


اضافه کردن پردازنده پیام به مسیر کلاینت (client pipeline)

برای اضافه کردن پردازنده های پیام سفارشی به HttpClient کافی است که از متد HttoClientFactory.Create استفاده کنیم. به شکل زیر:

HttpClient client = HttpClientFactory.Create(new Handler1(), new Handler2(), new Handler3());

پردازنده های پیام به ترتیبی فراخوانی می شوند که به متد Create ارسال می کنیم. از انجا که پردازنده های پیام تو در تو هستند، پیام پاسخ در مسیر دیگری حرکت می کند. بدین معنی که آخرین پردازنده پیام اولین پردازنده ای است که پیام پاسخ را دریافت می کند.


منابع:
   
ASP.NET Web API
ASP.NET MVC - Web API


پست های مرتبط

نام را وارد کنید
تعداد کاراکتر باقیمانده: 1000
نظر خود را وارد کنید