1. 405 Not Allowed


한창 작업을 하던 도중, 프론트 측에서 특정 API를 요청하니 405 Error가 떨어진다고 help 요청이 들어왔다.

405 Error에 대해 검색해보니, 문제의 원인은 프론트의 Api method( Get 혹은 Post )와 Api 서버가 설정한 Api method가 다르다는 것이다.

예를 들어, 프론트에서는 Post 요청을 했는데, 서버에서는 해당 api 요청을 Get으로 받는 것과 같은 상황이다.

에러 원인을 확인하고, 문제의 프론트 요청이 제대로 되어 있는지, 백엔드 서버가 제대로 되어있는지 확인하러 갔다.

  const tryLogin = async() => {
    Axios.post('http://hufsScheduleSystem/web/v1/user/Login', {
     "studentNumber": id, "password": password 
    }, config)
      .then(async (response) => {
        console.log(response);
        await doSignin(response.data.data.userId);       
    })
      .catch(async function (error) {
        console.log(error);
        alert("아이디/비밀번호를 확인해주세요!");
      });
      
    reset();
  }

Axios.post를 통해 Post 요청을 제대로 하고 있다. 프론트 쪽 문제는 아닌 듯 하다.

@RestController
@RequiredArgsConstructor
@RequestMapping("web/v1/user")
@CrossOrigin(origins = "*")
public class UserController {
    private final UserService userService;
    private final ResponseService responseService;

    @PostMapping("/Login")
    public CommonResult login(@RequestBody UserDto.loginReq login)throws Exception {
        return responseService.getSingleResult(userService.login(login));
    }
}

@PostMapping이 제대로 설정되어 있다.....?
뭐지,,,, 이제부터 슬 당황하기 시작했다.
프론트에서는 Post 요청을 제대로 하고 있고, 백에서도 PostMapping을 통해 제대로 받고 있기 때문이다.
여기서 이 문제가 더 골치 아팠던 이유는 평소에는 잘 되던 api가 어느날 갑자기 안 된다는 것이다.
뭐가 문제일까 고민을 해보다 서버에 문제가 있나? 서버 쪽을 들여다봤다.

2. Nginx가 문제인가?


본 토이 프로젝트는 현재 ec2의 Linux ami 위에서 Nginx를 통해 서비스되고 있다.
가끔 잘 되던 프로젝트에서 500 서버 에러를 뱉는다고 해서 확인해보면 동료 중 누군가 RDS - server 보안그룹 ip를 건드려서 문제가 발생하기도 하기에, 서버 쪽에서 문제가 있을 수도 있겠다라는 생각을 해봤다.
( 전혀 관계 없는 문제이지만 암튼 서버에서도 문제가 발생할 수 있다는 가능성을 생각했다는 의미입니다. )

Post 메소드를 왜 안 먹는 걸까?
그러다 이런 포스트를 봤다.
https://www.bonbon.io/how-to-enable-post-to-static-pages-running-via-nginx
요는 이렇다.

nginx에서는 .html 파일에 대한 post 요청을 지원하지 않는다.

처음에는 저 문장을 제대로 이해하지 못하고 그저 Post 요청을 지원하지 않는다. 라는 부분에 꽂혔다.

그러고는 생각했다.
What?? Post 메소드를 지원을 안해?? 그럼 nginx를 통해 돌아가는 그 수많은 애플리케이션들은 모두 Get 메소드만 사용한다는 건가?? 이게 말이 돼??
그러고는 위 포스트에서는 해결법으로 405 에러에 대해 강제로 기존 uri와 200 코드를 떨구는 방법을 제시했다.
그저 문제 해결을 위해 질주하던 나는 바로 시도했고 결과는 더욱 괴랄해졌다.

로그인 시도 -> 잠시 405 페이지 -> 다시 원래 로그인 페이지

잠깐 멘탈을 잡고 다시 보니 .html 파일에 대한 이란 표현이 이제야 보였다.

이 표현을 다시 보고 내린 결론은 이렇다.
위 문장은 Nginx가 정적 데이터에 대해서 get 요청만 가능하다는 것이지 Api서버에 대한 요청과는 전혀 다른 말이라는 것이다.

잠깐 놓았던 정신을 다시 붙들고 생각해보니 당연한 이야기가 아닌가?
아무튼 그렇다면 문제는 다시 api 서버가 아닐까 한다.

3. Backend가 문제인가?


그렇다기에는 너무나도 명확하게 @PostMapping이 달려있다.
서얼마 호오옥시 다른 @GetMapping이랑 요청 경로가 같을 확률은 없었다.

이참에 @PostMapping을 뜯어보니

@PostMapping("/Login")
@RequestMapping(value="/Login", method={RequestMethod.Post})

로 위 두 주석은 같은 의미를 같는다.

서버문제는 아닌 듯하다. 그렇다면 프론트에 뭔가 문제가 있는게 아닐까?

4. React가 문제인가?


로그인 페이지는 리액트를 아직 한 번도 해보지 않은 동료를 위해 처음 프로젝트를 시작할 때 내가 짰었던 페이지였다. 그런데 내가 최초에 짰던 코드와 사뭇 다른 점을 포착했다.

	<form onSubmit={tryLogin} method="POST"> 
          <div className="idPassword">
            <Input
              placeholder={"ex)195002215"}
              name={"id"}
              onChange={onChange}
              value={id}
              text={"학번"}
            />
            <Input
              placeholder={"ex)12345678"}
              name={"password"}
              type={"password"}
              onChange={onChange}
              value={password}
              text={"비밀번호"}
            />
          </div>
          <div className="idPassword">

              <button type={"submit"}className={"login"} name={"로그인"} onClick={tryLogin} > 로그인 </button>

              <Button onClick={toSignup} name={"회원가입"} value={"signUp"} />
          </div>
        </form>

저기 form tag가 보이는가.
(현재는 문제를 해결해서 정확하게 이전 코드는 아니지만)

5. Form tag가 문제였다.


왜 form tag를 추가했을까?
동료에게 물어봤다.
생각보다 신박한 답변이었다.
"아이디, 비밀번호를 입력하고 버튼을 클릭하는 대신 엔터를 치면 로그인 버튼을 누르는 효과를 주고 싶었다."
의도 자체는 타당한듯 했다. 자세히 보면 저 위에 로그인과 회원가입이 각각 button과 Button임을 볼 수 있다. 기존에 나는 Button 태그로 짰었는데, 엔터=로그인버튼 클릭 효과를 의도한 동료가 form태그로 감싸고, 기본 button tag로 바꿨던 것이다.

api 자체는 tryLogin 함수를 호출하면서 해당 함수에서 작동하는 건데, form 태그가 어떻게 혼선을 주는 걸까. form 태그 안에서 발생하는 api를 다 get으로 감싸버리나?

하는 생각에 코드를 보니,

<form onSubmit={tryLogin} method="POST"> 

이렇다. 즉, tryLogin 함수 안에 있는 axios.post 요청과 별개로, form 태그 안에 action으로 경로 설정을 해주지 않아 현재 url을 post요청하는 것이다. 그러니 nginx에서 정적데이터를 post 요청해? 하고 405 에러를 던저주는 것이다.

이러한 추측은 method를 get 으로 바꿔주면서 조금 더 확실해졌다. method를 get으로 바꾸니, 콘솔에는 로그인 성공! 메세지와 함께 userId를 받아오는데, 페이지는 메인 페이지로 넘어갔다가 다시 로그인 페이지로 돌아왔다. tryLogin 함수 내에서 로그인 성공할 경우, 다음 페이지로 이동하는 함수를 호출해서 페이지를 이동했다가, form이 요청했던 로그인 페이지에 대한 결과를 받아서 다시 로그인 페이지로 돌아오는 것이다.

이상, 405에러 대 탐방기를 마친다.

profile
개발자를 위한 개발자입니다.

1개의 댓글

comment-user-thumbnail
2020년 10월 4일

안녕하세요! 리액트 배포를 하고 있는 학생입니다!
작성하신 글 덕분에 도움이 많이 되었습니다 감사합니다.
한가지 여쭤볼 것이 있습니다.
배포하기 전에는 로그인이 잘 되었는데, 리액트 프로젝트를 yarn build를 하고 s3서버에 배포한 뒤에 로그인을 하면,
405에러가 뜹니다..
이거에 대한 해결방법이 작성하신 글을 보고, 제가 이해한 방법은 login에서 form 태그에 method="POST"를 명시해서 요청하는 것이 맞나요?? ㅎㅎ

답글 달기