API 요약

이 장은 API 표면 전체를 나열하며, 각 항목은 해당 내용을 설명하는 장을 가리킨다.

공개된 모든 패키지는 JSpecify로 @NullMarked되어 있으므로, 시그니처에 @Nullable이 명시되지 않은 한 파라미터나 반환값은 null을 배제한다. NullAway나 IntelliJ IDEA처럼 JSpecify를 읽는 도구는 non-null 파라미터에 도달하는 null을 보고한다.

App이 제공하는 것

  • 라우트: get, post, put, patch, delete, head, options, 각각 (path, description, handler) 형태로도 제공되며, path(prefix, group → …​)가 있다; 패턴과 매칭 순서는 라우팅에서 다룬다

  • 인트로스펙션: routes()는 등록된 모든 라우트를 Route(method, path, description)로 등록 순서대로 보고한다. hooks()는 그 곁에서 동작하는 필터와 상태 페이지를 Hook.BeforeRequest(path), Hook.BeforeRoute(path), Hook.AfterRoute(path), Hook.StatusPage(status), Hook.EveryResponse()로 보고한다. spider-silk-openapi는 라우트 목록을 OpenAPI 3.1 문서로 바꾼다

  • 필터: 라우팅 전의 beforeRequest(filter) / beforeRequest(path, filter), beforeRoute(filter) / beforeRoute(path, filter), 같은 형태의 afterRoute, 그리고 after 필터가 보지 못하는 응답까지 포함해 모든 응답에 적용되는 responseFilter(filter)

  • 로깅: requestLogger((request, completion) → …​), RequestCompletion에는 서블릿 상태, 경과 시간, 응답 정의, 전송 실패, 그리고 요청에 응답하게 된 예외가 담긴다; 요청 로깅을 참고한다

  • 에러: exception(Type, handler), 등록된 타입 중 가장 구체적인 것이 이기고 HttpException은 자신의 타입이나 하위 타입의 핸들러에만 도달한다, statusPage(HttpStatus, handler)

  • 응답 전반에 걸친 관심사: cors(Cors), gzip() / gzip(Gzip), securityHeaders() / securityHeaders(SecurityHeaders). 각각은 호출하기 전까지 꺼져 있고, 정적 파일과 에러를 포함한 모든 응답에 적용되므로 필터가 아니다; CORS, 압축, 보안을 참고한다

  • 본문 한도: bodyLimits(BodyLimits)이며, BodyLimits.defaults()는 body()에 1MB, bodyNdjson의 한 줄에 1MB를 두고, BodyLimits.unlimited()는 둘 다 없앤다. 기본값은 호출하지 않아도 적용되며, 한도를 넘는 본문은 413으로 응답한다; 요청을 참고한다

  • 렌더링과 자산: templates(renderer), staticFiles(classpathRoot), staticFiles(StaticFiles…​). classpath:/jte 위의 jte와 루트의 classpath:/public이 기본값이므로 둘 다 호출하지 않아도 된다. StaticFiles.classpath(root)는 클래스패스 루트를, StaticFiles.directory(path)는 디스크의 디렉터리를 제공하고, 한 번에 주어진 여러 루트는 순서대로 읽히며, precompressed()는 빌드가 남겨둔 .br이나 .gz 형제 파일이 있으면 그 파일로 응답한다. FreeMarkerTemplates, HandlebarsTemplates, ThymeleafTemplates는 각자의 모듈에 있는 TemplateRenderer 구현체다

  • 서버: start() / start(port), stop(), join(), port(), server(factory). 어떤 방식으로 시작했든 AppServlet이 애플리케이션을 서비스하는 동안 등록은 닫힌다: 그때 추가되는 라우트, 필터, 설정은 IllegalStateException을 던지고, stop()이 등록을 다시 연다. Cors, Gzip, SecurityHeaders, StaticFiles, BodyLimits는 등록될 때 복사된다

WebRequest가 제공하는 것

  • 요청 정보: method(), path(), header(name), 반복된 헤더를 위한 headers(name), 전체를 위한 headers(), contentType(), queryString(), isSecure(), scheme(), host(), remoteAddress(), 그리고 서블릿 요청 자체를 위한 raw(). host()는 포트가 스킴의 기본값이 아닐 때 포트를 함께 담으므로, scheme(), host(), path()로 절대 URL을 다시 만들 수 있다

  • 매칭된 라우트: route(), 응답한 Route로 그 패턴과 설명을 담는다; 라우팅 전과 매칭된 라우트가 없는 곳에서는 null이며, 라우트 인트로스펙션을 참고한다

  • 경로 변수: pathParam, pathParamLong, pathParamEnum, 그리고 그 밖의 타입을 위한 pathParam(name, parser). 패턴이 선언한 적 없는 이름은 400이 아니라 IllegalStateException을 던진다. \{name*} 꼬리는 경로의 나머지를 슬래시까지 포함해 바인딩하고, 아무것도 매칭하지 않았을 때는 ""가 된다

  • 파라미터: param(없으면 400), paramOrNull(없으면 null), paramLong, paramBoolean(true 또는 false만), paramEnum, 각각 선택 파라미터를 위한 (…​, default) 형태가 있다; 반복된 값을 위한 params(name)

  • 그 밖의 타입: param(name, parser)와 param(name, parser, default), 파서는 UUID::fromString이나 LocalDate::parse 같은 Function<String, T>다. IllegalArgumentException이나 DateTimeException을 던지는 파서는 파라미터 이름을 밝히며 400으로 응답한다

  • 한 출처만: queryParam(name)과 formParam(name)은 값을 요구하며 없으면 400으로 응답한다. queryParamOrNull(name)과 formParamOrNull(name)은 없으면 null을 반환한다. 파서 오버로드도 (…​, parser, default) 형태로 기본값을 주지 않는 한 값을 요구한다

  • 쿼리 대 폼: queryParam, queryParams, formParam, formParams

  • 콘텐츠 협상: accepts(types…​)는 제시된 타입 중 호출자가 선호하는 타입으로 응답하고, 어느 것도 받지 않으면 406이다; 파싱된 Accept 헤더를 위한 acceptedTypes()

  • 클라이언트가 보낸 쿠키: cookie(name), cookies()

  • 본문: body(), bodyJson(), bodyJson(reader)(리더가 거부하는 본문에는 400, 한도를 넘는 본문에는 413). 바이트는 한 번 읽어 보관하므로 모든 호출이 본문 전체를 답하며, bodyJson은 그 바이트를 도착한 그대로 파싱한다

  • 멀티파트 업로드: file(name)(없으면 400), fileOrNull(name)(없으면 null), 여러 파일을 담은 필드를 위한 files(name), 그리고 컨테이너가 거부한 업로드에는 셋 모두 413이나 400. UploadedFile은 fileName(), contentType(), size(), bytes(), text(), inputStream(), writeTo(path)를 제공한다

  • 읽지 않은 본문: bodyStream()과 bodyReader()는 다른 라이브러리의 파서를 위해 본문을 넘겨주며, 둘 다 보관된 텍스트와 섞이지 않는다: 나중에 호출된 쪽이 IllegalStateException을 던진다. bodyNdjson(reader)는 줄 단위로 구분된 JSON을 지연 읽기하고, 줄 번호를 밝히며 400으로 응답하거나, 한도를 넘는 줄에는 413으로 응답한다; 대용량 JSON과 NDJSON과 외부 JSON 바인딩을 참고한다

  • 세션: session()은 WebSession을 돌려주며, 그 get(key, Class<T>)는 지정한 타입으로 읽고 실패하면 그 줄에서 키와 두 타입을 밝히고, get(key)는 캐스트를 호출 지점에 맡기고, set(key, value)는 쓰고, remove(key)는 하나를 제거하고, invalidate()는 세션을 끝낸다

  • 플래시: flash(key, value), 리다이렉트 뒤의 요청에서 flashed(key)로 정확히 한 번 읽히며, null 값은 세션 속성에서와 마찬가지로 키를 제거한다

  • 에러: statusPage(status, handler) 핸들러 안의 errorMessage()

WebResponse가 제공하는 것

  • 본문: html, text, rawJson(text), json(JsonValue), json(value, writer), bytes(contentType, data), template(name), template(name, model), stream(contentType, out → …​), file(path), sse(stream → …​), raw((req, res) → …​)

  • Model.of(…​)는 `template(name, model)`이 받는 불변 Model을 null 값을 담을 수 있는 항목 열 개까지로 만든다; 템플릿을 참고한다

  • model.with(key, value)는 항목 하나를 더한 모델을, model.asMap()은 렌더러를 위한 읽기 전용 맵을 답한다

  • jte-models가 생성한 메서드로 부르는 jte 템플릿은 html(templates.deck(deck).render())로 보낸다; 템플릿을 참고한다

  • file(path)는 콘텐츠 타입을 이름에서, 길이를 파일에서 읽는다. 읽을 수 있는 일반 파일이 아닌 경로는 UncheckedIOException을 던지므로, 없는 파일에 대한 404는 핸들러가 직접 작성한다

  • 값을 하나씩 쓰는 본문: 담아 두기에 너무 큰 배열을 위한 jsonArray(sink → …​), 한 줄에 값 하나를 쓰는 ndjson(sink → …​). 둘 다 내부적으로는 stream 본문이며, 라이터는 핸들러 예외 처리 밖에서 실행된다. 싱크는 JsonSink이며, 그 write(value)와 write(value, writer)는 각각 값 하나를 쓴다; 대용량 JSON과 NDJSON을 참고한다

  • 상태: empty(), empty(HttpStatus), noContent(), redirect(location), redirect(location, HttpStatus)

  • 덧붙이기: status, header, withoutHeader(name), contentType, vary(field), attachment, body, cookie(name, value), cookie(name, value, maxAge), cookie(Cookie), expireCookie

  • 되읽기: status(), header(name), headers(), cookies(), body(). 헤더 이름은 대소문자를 구분하지 않고 비교되며, 어떤 표기로 몇 번을 설정하든 한 필드는 하나의 값을 가진다. 두 번 보내야 하는 헤더는 raw를 통해 쓴다; 헤더를 참고한다

body()의 형태와 람다 계열에 대해서는 응답을 참고한다.

net.benelog.silkjson이 제공하는 것

이 패키지는 코어가 그 위에 지어진 JSON 라이브러리인 silk-json의 것이다.

  • 트리: Json.object(), Json.array(), Json.parse(text), Json.parse(utf8), 그리고 JsonObject, JsonArray, JsonPrimitive를 가진 JsonValue; JSON을 참고한다

  • 매핑: JsonOutput을 상대로 하는 람다 (value, out) → …​`인 `JsonWriter<T>; JsonInput을 상대로 하는 람다인 JsonReader<T>; 그리고 둘을 한꺼번에 가진 JsonCodec<T>. JsonWriter.list, JsonWriter.tree, JsonReader.object, JsonReader.list, JsonReader.tree, JsonCodec.of, JsonCodec.list가 이들을 만든다. writer.toJson(value), writer.toJsonBytes(value), reader.fromJson(text), reader.fromJsonBytes(utf8)는 요청 밖에서 하나를 실행한다

  • JsonOutput: object(), array(), end(), name(key), put(key, …​), putNull(key), value(…​), nullValue(), values(strings), newline(). JsonOutput.inMemory()는 toBytes()와 toJson()을, 또는 length()와 writeTo(stream)에 이은 release()를 답하고, JsonOutput.to(stream)은 flush()에서 버퍼를 보낸다

  • JsonInput: object(), array(), nextKey(), nextKeyIs(key), keyHead(), nextElement(), keyIs(key), key(), readString(), readLong(), readInt(), readDouble(), readFloat(), readNumber(), readOneOf(names), readBoolean(), readNull(), readValue(), readValueBytes(), skipValue(), end(), error(message), 그 바탕은 JsonInput.of(utf8)이나 JsonInput.of(text)다

  • JsonKey.of(name): put, name, keyIs를 위해 한 번만 인코딩한 멤버 이름

  • JsonKey.names(constants): put(key, name), value(name), readOneOf를 위해 한 번만 인코딩한 enum 상수 이름들

  • @JsonBound: 애너테이션이 붙은 타입이나 그것이 지명한 타입의 코덱을 silk-json-processor가 컴파일 시점에 <Type>Json.CODEC으로 생성한다; 생성되는 JSON 코덱을 참고한다