커피 한잔 마실 짧은 시간에 라라벨 미들웨어에 대해 이해할 수 있는 예를 한번 들어본다.


라라벨 공식 매뉴얼에서 미들웨어를 이렇게 설명하고 있다. 


미들웨어는 애플리케이션으로 들어온 HTTP 요청을 간편하게 필터링할 수 있는 방법을 제공합니다. 예를 들어, 라라벨은 애플리케이션의 사용자가 인증되었는지 검사하는 미들웨어를 내장하고 있습니다. 만약 인증되지 않은 사용자라면, 미들웨어는 그 사용자를 로그인 화면으로 리다이렉트 시킬 것입니다. 반대로, 인증된 사용자라면, 미들웨어는 애플리케이션에서 HTTP 요청이 계속해서 더 처리되도록 허용할 것입니다.

한마디로 HTTP를 통해 들어오는 모든 요청을 처리하기 전, 또는 후에 추가적인 어떤 작업을 하고 싶다면 미들웨어를 활용할 수 있다. 가령 인증이나 권한처럼 모든 소스에 반복적으로 들어가야 하는 코드가 있다면 그것을 대신 미들웨어에서 담당할 수 있도록 할 수 있다는 것이다. 


설명보다는 실제 간단한 예를 통해 미들웨어에 대해 이해하는 것이 더 도움이 될 수 있겠다 싶다. 


미들웨어를 만들어볼 것이다. 보통 API 서비스를 만들면 Ajax를 통해서만 요청을 할 수 있도록 하는 것이 일반적이다. 그래서 만일 웹브라우저의 주소창을 통해 접근한다면 비정상적인 API 호출로 보고 권한을 제한하여 해당 요청처리를 거부한다. 


이럴 때 쓸 수 있는 것이 라라벨의 미들웨어다.  



우선 프로젝트를 생성한다. 


$ composer create-project laravel/laravel twinsoul-app.test --prefer-dist -vvv


Composer를 통해 프로젝트를 생성한 후에는 테스트를 할 API 주소를 하나 만든다. 이 API를 Ajax로 호출하면 정상적으로 처리되도록 하고, 그렇지 않은 접근에 대해서는 간단히 권한이 없다는 페이지로 돌려보낼 것이다. 


이를 위해 먼저 컨트롤러를 생성한다. 컨트롤러명은 간단히 HomeController로 하자.


$ php artisan make:controller HomeController

Controller created successfully.


생성된 컨트롤러에 home이라는 메소드를 생성한다. 정상적으로 호출시 'hello home~!'이라는 메시지를 단순 출력한다. 

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller
{
    public function home()
    {
        return "hello home~!";
    }
}
?>

라우트를 정의하는 web.php 파일에 /home으로 HTTP 요청이 들어올 경우 이 메소드를 실행하도록 라우트 매핑을 등록한다.


Route::get('/home', 'HomeController@home')->middleware('onlyapi');

이때 middleware() 메소드를 통해 이 서비스를 처리할 때 'onlyapi'라는 이름의 미들웨어가 작동하도록 한다. 


그럼 이제 미들웨어를 생성하고 등록해보자.


$ php artisan make:middleware OnlyAcceptApiCall

Middleware created successfully.


프로젝트의 Middleware 폴더에 OnlyAcceptApiCall라는 이름의 미들웨어 클래스가 생성된 것을 볼 수 있다. 



이 미들웨어의 handle() 메소드에서 메소드가 return을 하기 전에 들어온 요청에 대해 내가 처리하고 싶은 로직을 넣으면 된다. 

<?php

namespace App\Http\Middleware;

use Closure;

class OnlyAcceptApiCall
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if(!$request--->ajax()) {
            abort(401, 'Ajax를 통한 API 호출만 가능합니다.');
        }

        return $next($request);
    }
}
?>

여기서는 ajax()라는 Request 객체의 메소드를 통해 들어온 요청이 Ajax call을 통해 들어온 요청인지를 판별하고 만일 그렇지 않을 경우에는 abort 헬퍼를 통해 HTTP Response 401로 돌려보낸다. 


ajax() 메소드가 어떻게 구현되어 있나 궁금해서 소스를 따라가보자.

<?php
/**
 * Determine if the request is the result of an AJAX call.
 *
 * @return bool
 */
public function ajax()
{
    return $this->isXmlHttpRequest();
}
?>


역시 같은 Request 객체의 isXmlHttpRequest() 메소드를 호출한다. 이 메소드를 따라가보자. 

<?php
/**
 * Returns true if the request is a XMLHttpRequest.
 *
 * It works if your JavaScript library sets an X-Requested-With HTTP header.
 * It is known to work with common JavaScript frameworks:
 *
 * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
 *
 * @return bool true if the request is an XMLHttpRequest, false otherwise
 */
public function isXmlHttpRequest()
{
    return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
}
?>
소스를 보면 결국 HTTP 요청의 헤더를 뒤져 X-Requested-With 항목의 값이 XMLHttpRequest이면 Ajax call로 본다는 것을 알 수 있다. 


이제 생성한 미들웨어 클래스를 라라벨 프레임워크에서 등록해야 사용할 수 있다. Kernel.php가 그 역할을 한다.

<?php
/**
 * The application's route middleware.
 *
 * These middleware may be assigned to groups or used individually.
 *
 * @var array
 */
protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    'onlyapi' => \App\Http\Middleware\OnlyAcceptApiCall::class,
];
?>
앞서 web.php에서 라우트를 등록할 때 'onlyapi'라는 이름의 미들웨어를 사용한다고 했으므로 여기서 등록할 때 이 이름을 사용해서 등록해야 한다. 만일 다른 이름으로 등록한다면 당연히 해당 미들웨어는 작동하지 않을 것이다.  

이제 테스트를 해보자.


먼저 Ajax가 아닌 브라우저 상에서 주소창에 http://twinsoul-app.test/home 과 같이 접근해보자. 그러면 Ajax 호출이 아니기 때문에 미들웨어에서 401 응답을 반환하게 되고 화면과 같은 라라벨의 기본 401 오류화면이 렌더링된다. 




다음으로는 API 호출시 사용하는 Postman을 통해 호출해본다. 이때 전송되는 헤더에 'X-Requested-With'  항목의 값으로 'XMLHttpRequest' 라는 값을 전송하면 이는 정상적인 Ajax 호출로 인식하여 미들웨어를 통과하게 된다. 따라서 화면과 같이 'hello home~!'라는 문자열이 반환된다.




물론 보통 API 호출의 경우 JSON 형식을 통해 값을 주고 받으나 여기서는 간단히 미들웨어를 설명하기 위해 예를 든 것이니 참고하면 되겠다. 


Posted by 라스모르
,