ASP.NET Core 미들웨어ASP.NET Core Middleware

작성자: Rick AndersonSteve SmithBy Rick Anderson and Steve Smith

예제 코드 살펴보기 및 다운로드(다운로드 방법)View or download sample code (how to download)

미들웨어란?What is middleware?

미들웨어는 요청 및 응답을 처리하는 응용 프로그램 파이프라인으로 어셈블리되는 소프트웨어입니다.Middleware is software that's assembled into an application pipeline to handle requests and responses. 각 구성 요소:Each component:

  • 요청을 파이프라인의 다음 구성 요소로 전달할지 여부를 선택합니다.Chooses whether to pass the request to the next component in the pipeline.
  • 파이프라인의 다음 구성 요소가 호출되기 전과 후에 작업을 수행할 수 있습니다.Can perform work before and after the next component in the pipeline is invoked.

요청 대리자는 요청 파이프라인을 빌드하는 데 사용됩니다.Request delegates are used to build the request pipeline. 요청 대리자는 각 HTTP 요청을 처리합니다.The request delegates handle each HTTP request.

요청 대리자는 Run, MapUse 확장 메서드를 사용하여 구성됩니다.Request delegates are configured using Run, Map, and Use extension methods. 개별 요청 대리자는 무명 메서드(인라인 미들웨어라고 함)로 인라인에서 지정되거나 다시 사용할 수 있는 클래스에서 정의될 수 있습니다.An individual request delegate can be specified in-line as an anonymous method (called in-line middleware), or it can be defined in a reusable class. 이러한 다시 사용할 수 있는 클래스 및 인라인 무명 메서드는 미들웨어 또는 미들웨어 구성 요소입니다.These reusable classes and in-line anonymous methods are middleware, or middleware components. 요청 파이프라인의 각 미들웨어 구성 요소는 파이프라인의 다음 구성 요소를 호출하거나 적절한 경우 체인을 단락(short-circuiting)하는 일을 담당합니다.Each middleware component in the request pipeline is responsible for invoking the next component in the pipeline, or short-circuiting the chain if appropriate.

HTTP 모듈을 미들웨어로 마이그레이션은 ASP.NET Core와 ASP.NET 4.x의 요청 파이프라인 간의 차이점을 설명하고 더 많은 미들웨어 샘플을 제공합니다.Migrate HTTP Modules to Middleware explains the difference between request pipelines in ASP.NET Core and ASP.NET 4.x and provides more middleware samples.

IApplicationBuilder로 미들웨어 파이프라인 만들기Creating a middleware pipeline with IApplicationBuilder

ASP.NET Core 요청 파이프라인은 이 다이어그램이 보여 주는 것과 같이 차례로 호출되는 요청 대리자의 시퀀스로 구성됩니다(실행의 스레드는 검은색 화살표를 따름).The ASP.NET Core request pipeline consists of a sequence of request delegates, called one after the other, as this diagram shows (the thread of execution follows the black arrows):

요청 수신을 표시하고, 세 가지 미들웨어를 통해 처리하는 요청 처리 패턴 및 응용 프로그램을 나가는 응답

각 대리자는 다음 대리자 전과 후에 작업을 수행할 수 있습니다.Each delegate can perform operations before and after the next delegate. 또한 대리자는 다음 대리자에 요청을 전달하지 않도록 결정할 수 있습니다. 이를 요청 파이프라인을 단락(short-circuiting)한다고 합니다.A delegate can also decide to not pass a request to the next delegate, which is called short-circuiting the request pipeline. 단락(short-circuiting)은 불필요한 작업을 방지하기 때문에 보통 바람직합니다.Short-circuiting is often desirable because it avoids unnecessary work. 예를 들어 정적 파일 미들웨어는 정적 파일에 대한 요청을 반환하고 나머지 파이프라인을 단락(short-circuit)할 수 있습니다.For example, the static file middleware can return a request for a static file and short-circuit the rest of the pipeline. 예외 처리 대리자는 파이프라인의 이후 단계에서 발생하는 예외를 catch할 수 있도록 파이프라인의 초기에 호출되어야 합니다.Exception-handling delegates need to be called early in the pipeline, so they can catch exceptions that occur in later stages of the pipeline.

가장 간단한 가능한 ASP.NET Core 앱은 모든 요청을 처리하는 단일 요청 대리자를 설정합니다.The simplest possible ASP.NET Core app sets up a single request delegate that handles all requests. 이 경우 실제 요청 파이프라인은 포함하지 않습니다.This case doesn't include an actual request pipeline. 대신, 단일 익명 함수가 모든 HTTP 요청에 대한 응답에 호출됩니다.Instead, a single anonymous function is called in response to every HTTP request.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello, World!");
        });
    }
}

첫 번째 app.Run 대리자는 파이프라인을 종료합니다.The first app.Run delegate terminates the pipeline.

app.Use를 사용하여 여러 요청 대리자를 함께 연결할 수 있습니다.You can chain multiple request delegates together with app.Use. next 매개 변수는 파이프라인의 다음 대리자를 나타냅니다.The next parameter represents the next delegate in the pipeline. (next 매개 변수를 호출하지 않고 파이프라인을 단락(short-circuit)할 수 있습니다.) 이 예제에서 설명하듯이 일반적으로 다음 대리자 전과 후 모두에서 작업을 수행할 수 있습니다.(Remember that you can short-circuit the pipeline by not calling the next parameter.) You can typically perform actions both before and after the next delegate, as this example demonstrates:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

경고

클라이언트에 응답을 전송한 후에 next.Invoke를 호출하지 마십시오.Don't call next.Invoke after the response has been sent to the client. 응답이 시작된 후 HttpResponse로 변경하면 예외를 throw합니다.Changes to HttpResponse after the response has started will throw an exception. 예를 들어 헤더, 상태 코드 등을 설정하는 것 같은 변경은 예외를 throw합니다.For example, changes such as setting headers, status code, etc, will throw an exception. next를 호출한 후 응답 본문에 작성하기:Writing to the response body after calling next:

  • 프로토콜 위반이 발생할 수 있습니다.May cause a protocol violation. 예를 들어, 명시된 content-length보다 긴 내용이 작성될 수 있습니다.For example, writing more than the stated content-length.
  • 본문 형식을 손상시킬 수 있습니다.May corrupt the body format. 예를 들어 CSS 파일에 HTML 바닥글 작성하기.For example, writing an HTML footer to a CSS file.

HttpResponse.HasStarted는 헤더가 이미 전송됐는지 또는 본문이 이미 작성됐는지 여부를 나타내는 유용한 힌트를 제공해줍니다.HttpResponse.HasStarted is a useful hint to indicate if headers have been sent and/or the body has been written to.

순서Ordering

미들웨어 구성 요소가 Configure 메서드에 추가되는 순서는 요청에서 호출되는 순서와 응답에 대한 역순서를 정의합니다.The order that middleware components are added in the Configure method defines the order in which they're invoked on requests, and the reverse order for the response. 이 순서 지정은 보안, 성능 및 기능에 중요합니다.This ordering is critical for security, performance, and functionality.

Configure 메서드(아래 참조)는 다음 미들웨어 구성 요소를 추가합니다.The Configure method (shown below) adds the following middleware components:

  1. 예외/오류 처리Exception/error handling
  2. 정적 파일 서버Static file server
  3. 인증Authentication
  4. MVCMVC
public void Configure(IApplicationBuilder app)
{
    app.UseExceptionHandler("/Home/Error"); // Call first to catch exceptions
                                            // thrown in the following middleware.

    app.UseStaticFiles();                   // Return static files and end pipeline.

    app.UseAuthentication();               // Authenticate before you access
                                           // secure resources.

    app.UseMvcWithDefaultRoute();          // Add MVC to the request pipeline.
}

위의 코드에서 UseExceptionHandler는 파이프라인에 추가된 첫 번째 미들웨어 구성 요소이므로 후속 호출에서 발생하는 모든 예외를 catch합니다.In the code above, UseExceptionHandler is the first middleware component added to the pipeline—therefore, it catches any exceptions that occur in later calls.

정적 파일 미들웨어는 파이프라인 초기에 호출되므로 요청을 처리하고 나머지 구성 요소를 통과하지 않고 단락(short-circuit)할 수 있습니다.The static file middleware is called early in the pipeline so it can handle requests and short-circuit without going through the remaining components. 정적 파일 미들웨어는 권한 부여 검사를 제공하지 않습니다.The static file middleware provides no authorization checks. wwwroot 아래의 항목을 비롯한 제공되는 모든 파일은 공개적으로 사용할 수 있습니다.Any files served by it, including those under wwwroot, are publicly available. 정적 파일을 보호하는 방법은 정정 파일을 참조하세요.See Static files for an approach to secure static files.

요청이 정적 파일 미들웨어에서 처리되지 않는 경우 인증을 수행하는 ID 미들웨어(app.UseAuthentication)로 전달됩니다.If the request isn't handled by the static file middleware, it's passed on to the Identity middleware (app.UseAuthentication), which performs authentication. ID는 인증되지 않은 요청을 단락(short-circuit)하지 않습니다.Identity doesn't short-circuit unauthenticated requests. ID가 요청을 인증하지만 MVC가 특정 Razor 페이지 또는 컨트롤러 및 작업을 선택한 후에만 권한 부여(및 거부)가 발생합니다.Although Identity authenticates requests, authorization (and rejection) occurs only after MVC selects a specific Razor Page or controller and action.

다음 예제는 정적 파일에 대한 요청이 응답 압축 미들웨어 전에 정적 파일 미들웨어에서 처리되는 미들웨어 순서를 설명합니다.The following example demonstrates a middleware ordering where requests for static files are handled by the static file middleware before the response compression middleware. 정적 파일은 이 미들웨어의 순서로 압축되지 않습니다.Static files are not compressed with this ordering of the middleware. UseMvcWithDefaultRoute의 MVC 응답은 압축될 수 있습니다.The MVC responses from UseMvcWithDefaultRoute can be compressed.

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();         // Static files not compressed
                                  // by middleware.
    app.UseResponseCompression();
    app.UseMvcWithDefaultRoute();
}

Use, Run 및 MapUse, Run, and Map

Use, RunMap을 사용하여 HTTP 파이프라인을 구성합니다.You configure the HTTP pipeline using Use, Run, and Map. Use 메서드는 파이프라인을 단락(short-circuit)할 수 있습니다(즉, next 요청 대리자를 호출하지 않는 경우).The Use method can short-circuit the pipeline (that is, if it doesn't call a next request delegate). Run은 규칙이며 일부 미들웨어 구성 요소는 파이프라인의 끝에서 실행되는 Run[Middleware] 메서드를 노출할 수 있습니다.Run is a convention, and some middleware components may expose Run[Middleware] methods that run at the end of the pipeline.

Map* 확장은 파이프라인 분기에 규칙으로 사용됩니다.Map* extensions are used as a convention for branching the pipeline. Map은 지정된 요청 경로의 일치를 기반으로 요청 파이프라인을 분기합니다.Map branches the request pipeline based on matches of the given request path. 요청 경로가 지정된 경로로 시작하는 경우 분기가 실행됩니다.If the request path starts with the given path, the branch is executed.

public class Startup
{
    private static void HandleMapTest1(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 1");
        });
    }

    private static void HandleMapTest2(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 2");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1", HandleMapTest1);

        app.Map("/map2", HandleMapTest2);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
        });
    }
}

다음 표는 앞의 코드를 사용하여 http://localhost:1234의 요청 및 응답을 보여 줍니다.The following table shows the requests and responses from http://localhost:1234 using the previous code:

요청Request 응답Response
localhost:1234localhost:1234 Hello from non-Map delegate.Hello from non-Map delegate.
localhost:1234/map1localhost:1234/map1 Map Test 1Map Test 1
localhost:1234/map2localhost:1234/map2 Map Test 2Map Test 2
localhost:1234/map3localhost:1234/map3 Hello from non-Map delegate.Hello from non-Map delegate.

Map이 사용되는 경우 일치하는 경로 세그먼트는 HttpRequest.Path에서 제거되고 각 요청에 대해 HttpRequest.PathBase에 추가됩니다.When Map is used, the matched path segment(s) are removed from HttpRequest.Path and appended to HttpRequest.PathBase for each request.

MapWhen은 지정된 조건자의 결과를 기반으로 요청 파이프라인을 분기합니다.MapWhen branches the request pipeline based on the result of the given predicate. Func<HttpContext, bool> 형식의 조건자는 파이프라인의 새 분기에 요청을 매핑하는 데 사용될 수 있습니다.Any predicate of type Func<HttpContext, bool> can be used to map requests to a new branch of the pipeline. 다음 예제에서 조건자는 쿼리 문자열 변수 branch의 존재를 검색하는 데 사용됩니다.In the following example, a predicate is used to detect the presence of a query string variable branch:

public class Startup
{
    private static void HandleBranch(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            var branchVer = context.Request.Query["branch"];
            await context.Response.WriteAsync($"Branch used = {branchVer}");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
                               HandleBranch);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
        });
    }
}

다음 표는 앞의 코드를 사용하여 http://localhost:1234의 요청 및 응답을 보여 줍니다.The following table shows the requests and responses from http://localhost:1234 using the previous code:

요청Request 응답Response
localhost:1234localhost:1234 Hello from non-Map delegate.Hello from non-Map delegate.
localhost:1234/?branch=masterlocalhost:1234/?branch=master Branch used = masterBranch used = master

Map은 중첩을 지원합니다. 예를 들면 다음과 같습니다.Map supports nesting, for example:

app.Map("/level1", level1App => {
       level1App.Map("/level2a", level2AApp => {
           // "/level1/level2a"
           //...
       });
       level1App.Map("/level2b", level2BApp => {
           // "/level1/level2b"
           //...
       });
   });

Map은 여러 세그먼트를 한 번에 일치시킬 수도 있습니다. 예를 들면 다음과 같습니다.Map can also match multiple segments at once, for example:

app.Map("/level1/level2", HandleMultiSeg);

기본 제공 미들웨어Built-in middleware

ASP.NET Core는 다음 미들웨어 구성 요소 및 추가되어야 하는 순서에 대한 설명과 함께 제공됩니다.ASP.NET Core ships with the following middleware components, as well as a description of the order in which they should be added:

미들웨어Middleware 설명Description 순서Order
인증Authentication 인증 지원을 제공합니다.Provides authentication support. HttpContext.User가 필요하기 전에.Before HttpContext.User is needed. OAuth 콜백에 대한 터미널.Terminal for OAuth callbacks.
CORSCORS 원본 간 리소스 공유를 구성합니다.Configures Cross-Origin Resource Sharing. CORS를 사용하는 구성 요소 이전.Before components that use CORS.
진단Diagnostics 진단을 구성합니다.Configures diagnostics. 오류를 생성하는 구성 요소 이전.Before components that generate errors.
전달된 헤더Forwarded Headers 프록시된 헤더를 현재 요청에 전달합니다.Forwards proxied headers onto the current request. 구성 요소가 업데이트된 필드를 사용하기 이전입니다(예: 체계, 호스트, 클라이언트 IP, 메서드).Before components that consume the updated fields (examples: scheme, host, client IP, method).
HTTP 메서드 재정의HTTP Method Override 들어오는 POST 요청이 메서드를 재정의하도록 허용합니다.Allows an incoming POST request to override the method. 업데이트된 메서드를 사용하는 구성 요소 앞입니다.Before components that consume the updated method.
HTTPS 리디렉션HTTPS Redirection HTTPS로 모든 HTTP 요청을 리디렉션합니다(ASP.NET Core 2.1 이상).Redirect all HTTP requests to HTTPS (ASP.NET Core 2.1 or later). URL을 사용하는 구성 요소 이전.Before components that consume the URL.
HSTS(HTTP 엄격한 전송 보안)HTTP Strict Transport Security (HSTS) 특별한 응답 헤더를 추가하는 보안 향상 미들웨어입니다(ASP.NET Core 2.1 이상).Security enhancement middleware that adds a special response header (ASP.NET Core 2.1 or later). 응답이 보내지기 이전 및 구성 요소가 요청을 수정한 이후입니다(예: 전달된 헤더, URL 다시 쓰기).Before responses are sent and after components that modify requests (for example, Forwarded Headers, URL Rewriting).
응답 캐싱Response Caching 응답 캐시에 대한 지원을 제공합니다.Provides support for caching responses. 캐싱이 필요한 구성 요소 이전.Before components that require caching.
응답 압축Response Compression 응답 압축에 대한 지원을 제공합니다.Provides support for compressing responses. 압축이 필요한 구성 요소 이전.Before components that require compression.
요청 지역화Request Localization 지역화 지원을 제공합니다.Provides localization support. 지역화 구분 구성 요소 이전.Before localization sensitive components.
라우팅Routing 요청 경로를 정의하고 제한합니다.Defines and constrains request routes. 경로 일치에 대한 터미널.Terminal for matching routes.
세션Session 사용자 세션 관리에 대한 지원을 제공합니다.Provides support for managing user sessions. 세션이 필요한 구성 요소 이전.Before components that require Session.
정적 파일Static Files 정적 파일 및 디렉터리 검색 처리에 대한 지원을 제공합니다.Provides support for serving static files and directory browsing. 요청이 파일과 일치하는 경우 터미널.Terminal if a request matches files.
URL 재작성URL Rewriting URL 재작성 및 요청 리디렉션에 대한 지원을 제공합니다.Provides support for rewriting URLs and redirecting requests. URL을 사용하는 구성 요소 이전.Before components that consume the URL.
WebSocketsWebSockets WebSocket 프로토콜을 활성화합니다.Enables the WebSockets protocol. WebSocket 요청을 수락하는 데 필요한 구성 요소 이전.Before components that are required to accept WebSocket requests.

미들웨어 작성Writing middleware

미들웨어는 일반적으로 클래스에서 캡슐화되고 확장 메서드로 노출됩니다.Middleware is generally encapsulated in a class and exposed with an extension method. 쿼리 문자열에서 현재 요청에 대한 문화권을 설정하는 다음 미들웨어를 고려합니다.Consider the following middleware, which sets the culture for the current request from the query string:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use((context, next) =>
        {
            var cultureQuery = context.Request.Query["culture"];
            if (!string.IsNullOrWhiteSpace(cultureQuery))
            {
                var culture = new CultureInfo(cultureQuery);

                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;
            }

            // Call the next delegate/middleware in the pipeline
            return next();
        });

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(
                $"Hello {CultureInfo.CurrentCulture.DisplayName}");
        });

    }
}

참고: 위의 샘플 코드는 미들웨어 구성 요소 만들기를 보여 주는 데 사용됩니다.Note: The sample code above is used to demonstrate creating a middleware component. ASP.NET Core의 기본 제공 지역화 지원은 전역화 및 지역화를 참조하세요.See Globalization and localization for ASP.NET Core's built-in localization support.

문화권을 전달하여 미들웨어를 테스트할 수 있습니다(예: http://localhost:7997/?culture=no).You can test the middleware by passing in the culture, for example http://localhost:7997/?culture=no.

다음 코드는 미들웨어 대리자를 클래스로 이동합니다.The following code moves the middleware delegate to a class:

using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks;

namespace Culture
{
    public class RequestCultureMiddleware
    {
        private readonly RequestDelegate _next;

        public RequestCultureMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var cultureQuery = context.Request.Query["culture"];
            if (!string.IsNullOrWhiteSpace(cultureQuery))
            {
                var culture = new CultureInfo(cultureQuery);

                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;

            }

            // Call the next delegate/middleware in the pipeline
            await _next(context);
        }
    }
}

참고

ASP.NET Core 1.x에서 미들웨어 Task 메서드의 이름은 Invoke이어야 합니다.In ASP.NET Core 1.x, the middleware Task method's name must be Invoke. ASP.NET 코어 2.0 이상에서는 이름이 Invoke 또는 InvokeAsync일 수 있습니다.In ASP.NET Core 2.0 or later, the name can be either Invoke or InvokeAsync.

다음 확장 메서드는 IApplicationBuilder를 통해 미들웨어를 공개합니다.The following extension method exposes the middleware through IApplicationBuilder:

using Microsoft.AspNetCore.Builder;

namespace Culture
{
    public static class RequestCultureMiddlewareExtensions
    {
        public static IApplicationBuilder UseRequestCulture(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestCultureMiddleware>();
        }
    }
}

다음 코드는 Configure에서 미들웨어를 호출합니다.The following code calls the middleware from Configure:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseRequestCulture();

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(
                $"Hello {CultureInfo.CurrentCulture.DisplayName}");
        });

    }
}

미들웨어는 해당 생성자에서 해당 종속성을 노출하여 명시적 종속성 원칙을 따라야 합니다.Middleware should follow the Explicit Dependencies Principle by exposing its dependencies in its constructor. 미들웨어는 응용 프로그램 수명당 한 번 생성됩니다.Middleware is constructed once per application lifetime. 서비스를 요청 내의 미들웨어와 공유해야 하는 경우 아래 요청당 종속성을 참조하세요.See Per-request dependencies below if you need to share services with middleware within a request.

미들웨어 구성 요소는 생성자 매개 변수를 통해 종속성 주입에서 해당 종속성을 확인할 수 있습니다.Middleware components can resolve their dependencies from dependency injection through constructor parameters. UseMiddleware<T>는 추가 매개 변수를 직접 수락할 수도 있습니다.UseMiddleware<T> can also accept additional parameters directly.

요청당 종속성Per-request dependencies

미들웨어는 요청당이 아닌 앱 시작 시 생성되므로 미들웨어 생성자에 의해 사용되는 범위가 지정된 수명 서비스는 각 요청 중에 다른 종속성 주입된 형식과 공유되지 않습니다.Because middleware is constructed at app startup, not per-request, scoped lifetime services used by middleware constructors are not shared with other dependency-injected types during each request. 범위가 지정된 서비스를 미들웨어와 다른 형식 간에 공유해야 하는 경우 이러한 서비스를 Invoke 메서드의 서명에 추가합니다.If you must share a scoped service between your middleware and other types, add these services to the Invoke method's signature. Invoke 메서드는 종속성 주입으로 채워지는 추가 매개 변수를 수락할 수 있습니다.The Invoke method can accept additional parameters that are populated by dependency injection. 예:For example:

public class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
    {
        svc.MyProperty = 1000;
        await _next(httpContext);
    }
}

추가 자료Additional resources