9.

djangoにおけるHTTPステータスコードの返し方

編集
この記事の要点
  • Spring MVC / REST で HTTP ステータスコードを指定する 4 つの方法
  • @ResponseStatus: メソッドに付与(最簡単)
  • ResponseEntity: メソッド戻り値で詳細制御
  • HttpServletResponse.setStatus: 低レベル制御
  • @ExceptionHandler + @ResponseStatus: 例外時のステータス

 

方法 1: @ResponseStatus

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)  // 201
    public UserDto create(@RequestBody UserCreateRequest req) {
        return userService.create(req);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)  // 204
    public void delete(@PathVariable Long id) {
        userService.delete(id);
    }
}

方法 2: ResponseEntity(柔軟)

@GetMapping("/{id}")
public ResponseEntity get(@PathVariable Long id) {
    return userService.findByIdOptional(id)
        .map(user -> ResponseEntity.ok(UserDto.from(user)))  // 200
        .orElseGet(() -> ResponseEntity.notFound().build());  // 404
}

@PostMapping
public ResponseEntity create(@RequestBody UserCreateRequest req) {
    UserDto created = UserDto.from(userService.create(req));
    URI location = URI.create("/api/users/" + created.getId());
    return ResponseEntity
        .created(location)  // 201 + Location ヘッダ
        .body(created);
}

@PutMapping("/{id}")
public ResponseEntity update(@PathVariable Long id, @RequestBody UserUpdateRequest req) {
    if (!userService.existsById(id)) {
        return ResponseEntity.notFound().build();
    }
    UserDto updated = UserDto.from(userService.update(id, req));
    return ResponseEntity.ok(updated);
}

// カスタムヘッダ付き
@GetMapping("/{id}/etag")
public ResponseEntity getWithEtag(@PathVariable Long id) {
    User user = userService.findById(id);
    return ResponseEntity.ok()
        .eTag(user.getVersion().toString())
        .header("X-Custom", "value")
        .body(UserDto.from(user));
}

方法 3: ResponseEntity.status() で任意コード

// 任意のステータス
return ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT).body(...);  // 418

// よく使うショートカット
ResponseEntity.ok(body)                              // 200
ResponseEntity.ok().build()                           // 200 (body なし)
ResponseEntity.created(location).body(body)          // 201
ResponseEntity.accepted().body(body)                  // 202
ResponseEntity.noContent().build()                    // 204
ResponseEntity.badRequest().body(error)               // 400
ResponseEntity.notFound().build()                     // 404
ResponseEntity.unprocessableEntity().body(errors)    // 422
ResponseEntity.internalServerError().body(error)      // 500

// HttpStatus enum
return ResponseEntity.status(HttpStatus.SEE_OTHER)
    .header("Location", "/new-location")
    .build();

方法 4: HttpServletResponse 直接(古い書き方)

@PostMapping("/something")
public void doSomething(HttpServletResponse response) throws IOException {
    response.setStatus(201);
    response.setHeader("Location", "/new-resource/123");
    response.getWriter().write("Created");
}

// 非推奨: ResponseEntity の方が型安全で読みやすい

例外でのステータスコード

① 例外クラスに @ResponseStatus

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(Long id) {
        super("User not found: " + id);
    }
}

// throw すると自動で 404
@GetMapping("/{id}")
public UserDto get(@PathVariable Long id) {
    return userService.findById(id)
        .orElseThrow(() -> new UserNotFoundException(id));
}

② @ExceptionHandler でステータス指定

@RestController
public class UserController {

    @ExceptionHandler(EntityNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleNotFound(EntityNotFoundException ex) {
        return new ErrorResponse("NOT_FOUND", ex.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map handleValidation(MethodArgumentNotValidException ex) {
        Map errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(e ->
            errors.put(e.getField(), e.getDefaultMessage())
        );
        return errors;
    }
}

③ ResponseStatusException (Spring 5+)

@GetMapping("/{id}")
public UserDto get(@PathVariable Long id) {
    if (!userService.existsById(id)) {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found");
    }
    return UserDto.from(userService.findById(id));
}

// 別パターン
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid data");
throw new ResponseStatusException(HttpStatus.CONFLICT, "Duplicate entry");
throw new ResponseStatusException(HttpStatus.FORBIDDEN);

主要 HTTP ステータスコード

コード意味用途
200 OK成功GET / PUT 成功
201 Created作成成功POST で新規作成
204 No Content成功 (ボディなし)DELETE / 一部 PUT
301 Moved Permanently恒久リダイレクトURL 変更
302 Found一時リダイレクトログイン後等
304 Not Modified変更なし (キャッシュ)ETag / If-Modified-Since
400 Bad Request不正なリクエストバリデーション失敗
401 Unauthorized未認証ログイン必要
403 Forbidden権限不足認証済だが許可なし
404 Not Foundリソースなし存在しない ID
405 Method Not AllowedHTTP メソッド不正GET 想定に POST 等
409 Conflict競合重複・楽観ロック失敗
422 Unprocessable Entity構文 OK・意味エラーバリデーション (Laravel デフォルト)
429 Too Many Requestsレート制限API 流量制御
500 Internal Server Errorサーバエラー未捕捉例外
502 Bad Gateway上流応答不正nginx の背後不調
503 Service Unavailableサービス停止メンテナンス・過負荷
504 Gateway Timeout上流応答タイムアウトnginx タイムアウト

REST API のステータス設計指針

操作成功失敗 (主)
GET /users/123200 (見つかった)404 (ない)
GET /users200 (空配列でも 200)500 (DB エラー等)
POST /users201 + Location ヘッダ400 (構文) / 422 (検証)
PUT /users/123200 (更新後 body) or 204404 / 409 (競合)
DELETE /users/123204 (本体なし)404
POST /login200401
権限なし-403

関連記事

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. クラスベースビュー(主流)の作り方とviewの分割
  2. 関数ベースビューの作り方とviewの分割
  3. URLディスパッチャー(ルーティング処理)
  4. GETとPOSTパラメータ受け取り
  5. クラスベースビューでGET/POSTリクエストの受け取り方
  6. クラスベースビューでテンプレートに値を渡す方法
  7. ビューでリダイレクト
  8. cookieの値の設定と取得
  9. HTTPステータスコードの返し方