Ти можеш використовувати анотацію @RequestMapping для відображення запитів на методи контролерів. Вона має різні атрибути для зіставлення за URL-адресою, HTTP-методом, параметрами запиту, заголовками та типами даних. ЇЇ можна використовувати на рівні класу для вираження загальних відображень або на рівні методу для звуження до конкретного відображення кінцевої точки. Існують також специфічні для HTTP-методу варіанти скорочення анотації @RequestMapping

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

Скорочення є кастомними анотаціями, які вказуються тому, що, мабуть, більшість методів контролера потрібно зіставити з конкретним HTTP-методом, а не використовувати анотацію @RequestMapping, яка за замовчуванням здійснює зіставлення з усіма HTTP-методами. Для вираження загальних відображень на рівні класу, як і раніше, необхідна @RequestMapping.

Наступний приклад має відображення на рівні типів та методів:

Java

@RestController
@RequestMapping("/persons")
class PersonController {
    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // ...
    }
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}
Kotlin

@RestController
@RequestMapping("/persons")
class PersonController {
    @GetMapping("/{id}")
    fun getPerson(@PathVariable id: Long): Person {
        // ...
    }
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    fun add(@RequestBody person: Person) {
        // ...
    }
}

Шаблони URI-ідентифікаторів

Методи, анотовані @RequestMapping, можна відобразити за допомогою шаблонів URL-адрес. Є дві альтернативи:

  • PathPattern — попередньо парсований шаблон, зіставлений за допомогою URL-адреси, також попередньо парсований як PathContainer. Це рішення, розроблене для використання в інтернет-середовищі, яке ефективно працює з параметрами кодування і шляху, а також забезпечує ефективне зіставлення.

  • AntPathMatcher — зіставлення рядкових шаблонів з рядковим шляхом. Це оригінальне рішення також використовується в конфігурації Spring для вибору ресурсів у classpath, файловій системі та інших місцях. Він менш ефективний, а введення рядкового шляху ускладнює ефективну роботу з кодуванням та іншими проблемами з URL-адресами.

PathPattern — це рекомендоване рішення для вебдодатків, і це єдиний варіант у роботі зі Spring WebFlux. До версії 5.3 AntPathMatcher був єдиним варіантом у роботі зі Spring MVC і залишається ним за замовчуванням. Однак PathPattern можна активувати в конфігурації MVC.

PathPattern підтримує той же синтаксис шаблонів, що й AntPathMatcher. До того ж, він також підтримує шаблон захоплення, наприклад, {*spring}, для зіставлення 0 або більше сегментів шляху в кінці шляху. PathPattern також обмежує використання знака ** для порівняння кількох сегментів шляху таким чином, що він дозволений лише наприкінці шаблону. Це дозволяє уникнути випадків неоднозначності при виборі найкращого шаблону узгодження цього запиту. Повний синтаксис шаблонів наведено в розділах, присвячених PathPattern та AntPathMatcher.

Деякі приклади шаблонів:

  • "/resources/ima?e.png " — зіставлення з одним символом у сегменті шляху

  • "/resources/*.png" — зіставлення з нулем або більше символів у сегменті шляху

  • "/resources/**" — зіставлення з кількома сегментами шляху

  • "/projects/{project}/versions" — зіставлення з сегментом шляху та запис його як змінного

  • "/projects/{ project:[a-z]+}/versions" — зіставлення та захоплення змінної за допомогою регулярного виразу

До захоплених змінних URI-ідентифікаторів можна отримати доступ за допомогою анотації @PathVariable. Наприклад:

Java

@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // ...
}
Kotlin

@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
    // ...
}

Ти можеш оголошувати змінні URI-ідентифікаторів на рівні класів та методів, як показано в наступному прикладі:

Java

@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
    @GetMapping("/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}
Kotlin

@Controller
@RequestMapping("/owners/{ownerId}")
class OwnerController {
    @GetMapping("/pets/{petId}")
    fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
        // ...
    }
}

Змінні URI-ідентифікаторів автоматично перетворюються на відповідний тип, або генерується TypeMismatchException. Прості типи (int, long, Date тощо) підтримуються за замовчуванням, але можна зареєструвати засоби підтримки будь-якого іншого типу даних. Див. розділ "Перетворення типів" та "DataBinder".

Можна явно привласнити імена змінним URI-ідентифікаторів (наприклад, @PathVariable("customId")), але також можна і не вказувати ці відомості, якщо імена однакові, а код компілюється з використанням налагоджувальної інформації або з прапором компілятора -parameters на Java 8.

Синтаксис {varName:regex} повідомляє змінну URI-ідентифікатора за допомогою регулярного виразу, який має синтаксис {varName:regex}. Наприклад, якщо дана URL-адреса "/spring-web-3.0.5.jar", то наступний метод отримує ім'я, версію та розширення файлу:

Java

@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
    // ...
}
Kotlin

@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
fun handle(@PathVariable name: String, @PathVariable version: String, @PathVariable ext: String) {
    // ...
}

Шаблони шляхів URI-ідентифікатора також можуть містити вбудовані плейсхолдери ${…}, які розпізнаються під час запуску за допомогою PropertySourcesPlaceholderConfigurer у локальних, системних джерелах властивостей, джерелах властивостей оточення та ін. Можна використовувати це, наприклад, для параметризації базової URL-адреси на основі деякої зовнішньої конфігурації.

Порівняння шаблонів

Якщо URL-адресі відповідає декілька шаблонів, необхідно вибрати найбільш підходящий. Це робиться одним із наступних способів залежно від того, чи дозволено синтаксично парсований PathPattern до використання чи ні:

І те, й інше допомагає сортувати шаблони, причому конкретніші з них знаходяться зверху. Шаблон є менш конкретним, якщо в ньому менше змінних URI-ідентифікаторів (рахується за 1), одиночних підстановочних знаків (вважається за 1) та подвійних підстановочних знаків (рахується за 2). За рівних показників обирається більш довгий шаблон. За однакових показників і довжини вибирається шаблон, в якому більше змінних URI-ідентифікаторів, ніж підстановочних знаків.

Шаблон відображення за замовчуванням (/**) виключається з підрахунку і завжди сортується останнім. До того ж, шаблони з префіксами (такі як /public/**) вважаються менш конкретними, ніж інші шаблони, що не містять подвійних підстановочних знаків.

Для отримання повної інформації перейди за вказаними вище посиланням на описи шаблонів компараторів.

Суфіксальний збіг

Починаючи з версії 5.3, Spring MVC за замовчуванням більше не виконує зіставлення шаблонів із суфіксами .*, якщо контролер, зіставлений з /person, також неявно зіставляється з /person.*. Як наслідок, розширення шляхів більше не використовуються для інтерпретації типу запитуваного вмісту для відповіді. Наприклад, /person.pdf, /person.xml тощо.

Використання розширень файлів у такий спосіб було необхідно, коли браузери надсилали заголовки Accept, які було важко інтерпретувати послідовно. Нині в цьому вже немає потреби, і використання заголовка Accept має бути найкращим вибором.

З часом використання розширень імен файлів виявилося проблематичним з цілої низки причин. Це може викликати неоднозначність під час накладення з використанням змінних URI-ідентифікаторів, параметрів шляху та кодування URI-ідентифікаторів. Обґрунтування правильності авторизації та безпеки на основі URL (докладніше див. наступний розділ) також ускладнюється.

Щоб повністю відключити використання розширень шляхів у версіях до 5.3, встанови таке:

  • useSuffixPatternMatching(false)

  • favorPathExtension(false)

Наявність способу запиту типів вмісту, відмінного від заголовка "Accept", може бути корисною, наприклад, при введенні URL-адреси в браузері. Безпечною альтернативою розширення шляху є використання стратегії параметрів запиту. Якщо тобі необхідно використовувати розширення файлів, подумай про те, щоб обмежити їх списком явно зареєстрованих розширень через властивість mediaTypes для ContentNegotiationConfigurer.

Суфіксальний збіг та RFD

Вектор атаки відображеного завантаження файлів (Reflected File Download/RFD) схожий на XSS тим, що він ґрунтується на тому, що вступні дані запиту (наприклад, параметр запиту та змінна URI-ідентифікатора) відображаються у відповіді. Однак замість вставки JavaScript до HTML, вектор атаки RFD засновано на перемиканні браузера на виконання завантаження та обробки відповіді як скрипта, що виконується, при наступному подвійному кліку.

У Spring MVC методи, позначені анотацією @ResponseBody, та методи ResponseEntity знаходяться в зоні ризику, оскільки можуть візуалізувати різні типи контенту, які клієнти можуть вимагати через розширення шляхів URL-адреси. Відключення суфіксального зіставлення шаблонів і використання розширень шляху для узгодження вмісту знижують ризик, але не є достатніми для запобігання RFD-атакам.

Для запобігання RFD-атакам перед візуалізацією тіла відповіді Spring MVC додає заголовок Content-Disposition:inline;filename=f.txt, щоб запропонувати фіксований та безпечний файл завантаження. Це робиться лише в тому випадку, якщо шлях URL-адреси містить розширення файлу, що не дозволене як безпечне і не зареєстроване явно для узгодження вмісту. Однак це може мати побічні ефекти, якщо URL-адреси вводяться безпосередньо в браузері. Багато поширених розширень шляхів за замовчуванням дозволені як безпечні. Програми з кастомними реалізаціями HttpMessageConverter можуть явно реєструвати розширення файлів для узгодження вмісту, щоб уникнути додавання заголовка Content-Disposition для цих розширень.

Додаткові рекомендації, пов'язані з RFD, див. у розділі "CVE-2015-5211".

Потрібні типи даних

Можна звузити діапазон відображення запитів на основі Content-Type запиту, як показано в наступному прикладі:

Java
@PostMapping(path = "/pets", consumes = "application/json") 
public void addPet( @RequestBody Pet pet) {
    // ...
}
  1. Використання атрибуту consumes для звуження діапазону відображення за типом вмісту.
Kotlin
@PostMapping("/pets", consumes = ["application/json" ]) 
fun addPet(@RequestBody pet: Pet) {
    // ...
}}
  1. Використання атрибуту consumes для звуження діапазону відображення за типом вмісту.

Атрибут consumes також підтримує вирази заперечення. Наприклад, !text/plain означає будь-який тип вмісту, відмінний від text/plain.

Можна оголошувати загальний атрибут consumes на рівні класу. Однак, на відміну від більшості інших атрибутів відображення запитів, при використанні на рівні класу атрибут consumes на рівні методу радше перевизначає, а не розширює оголошення на рівні класу.

MediaType надає константи для часто використовуваних типів середовищ передачі даних, таких як APPLICATION_JSON_VALUE та APPLICATION_XML_VALUE.

Типи даних, що отримуються

Можна звузити діапазон відображення запитів на основі заголовка запиту Accept та списку типів вмісту, які виробляє метод контролера, як показано в наступному прикладі:

Java
@GetMapping(path = "/pets/{petId}", produces = "application/json") 
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}
  1. Використання атрибуту produces для звуження діапазону відображення за типом вмісту.
Kotlin
@GetMapping("/pets/{petId}", produces = ["application/json"]) 
@ResponseBody
fun getPet( @PathVariable petId: String): Pet {
// ...
}
  1. Використання атрибуту produces для звуження діапазону відображення типу вмісту.

Тип середовища передачі даних може визначати набір символів. Підтримуються заперечні вирази — наприклад, !text/plain означає будь-який тип вмісту, відмінний від "text/plain".

Можна оголошувати загальний атрибут produces на рівні класу. Однак, на відміну від більшості інших атрибутів відображення запитів, при використанні на рівні класу атрибут produces на рівні методу швидше перевизначає, а не розширює оголошення на рівні класу.

MediaType надає константи для часто використовуваних типів середовищ передачі даних, таких як APPLICATION_JSON_VALUE та APPLICATION_XML_VALUE.

Параметри, заголовки

Можна звузити діапазон відображення запитів на основі умов параметрів запиту. Ти маєш змогу перевірити наявність параметра (myParam), його відсутність (!myParam) або певне значення(myParam=myValue). У цьому прикладі показано, як перевірити наявність певного значення:

Java
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue ") 
public void findPet(@PathVariable String petId) {
    // ...
}
  1. Перевірка того, чи дорівнює myParam значенню myValue.
Kotlin
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) 
fun findPet(@PathVariable petId: String) {
// ...
}
  1. Перевірка того, чи дорівнює myParam значенню myValue.

Те саме можна використовувати і з умовами заголовка запиту, як показано в наступному прикладі:

Java
@GetMapping(path = "/pets", headers = "myHeader=myValue") 
public void findPet(@PathVariable String petId) {
// ...
}
  1. Перевір, чи дорівнює myHeader значення myValue.
Kotlin
@GetMapping("/pets", headers = ["myHeader=myValue"]) 
fun findPet( @PathVariable petId: String) {
    // ...
}
Ти можеш порівняти Content-Type та Accept з умовою заголовків, але краще замість цього використовувати consumes і produces.

HTTP-методи HEAD, OPTIONS

@GetMapping@RequestMapping(method=HttpMethod.GET)) прозоро підтримують HTTP-метод HEAD для відображення запитів. Методи контролера не потрібно змінювати. Функція-обгортка відповіді, що використовується в javax.servlet.http.HttpServlet, забезпечує встановлення заголовка Content-Length на кількість записаних байт (без фактичного запису у відповідь).

@GetMapping@RequestMapping(method=HttpMethod.GET)) неявно відображені на HTTP-метод HEAD і підтримують його. Запит за HTTP-методом HEAD обробляється так само, як і за HTTP-методом GET, за винятком того, що замість запису тіла підраховується кількість байт і встановлюється заголовок Content-Length.

За замовчуванням HTTP-метод OPTIONS обробляється шляхом встановлення заголовка відповіді Allow до списку HTTP-методів, перерахованих у всіх методах, що позначені анотацією @RequestMapping та мають відповідні шаблони URL-адрес.

У разі анотації @RequestMapping без оголошення HTTP-методів, заголовок Allow встановлюється в GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS. Методи контролера завжди повинні оголошувати підтримувані HTTP-методи (наприклад, за допомогою варіантів, специфічних для HTTP-методів: @GetMapping, @PostMapping та інші).

Ти можеш явно відобразити метод, анотований @RequestMapping, на HTTP-метод HEAD та HTTP-метод OPTIONS, але в стандартних випадках це робити не обов'язково.

Кастомні анотації

Spring MVC підтримує використання зіставних анотацій для відображення запитів. Це анотації, які є мета-анотаціями @RequestMapping і складені для повторного оголошення підмножини (або всіх) атрибутів, позначених анотацією @RequestMapping, з більш вузькою, більш конкретною метою.

@GetMapping, @PostMapping, @PutMapping, @DeleteMapping та @PatchMapping є прикладами зіставних анотацій. Вони передбачаються тому, що, мабуть, більшість методів контролера потрібно буде зіставити з конкретним HTTP-методом замість використання анотації @RequestMapping, яка за замовчуванням зіставляється з усіма HTTP-методами. Якщо тобі потрібний приклад зіставних анотацій, переглянь, як вони оголошуються.

Spring MVC також підтримує кастомні атрибути відображення запитів із кастомно встановленою логікою відображення запитів. Це більш розширений варіант, який вимагає створення підкласу RequestMappingHandlerMapping та перевизначення методу getCustomMethodCondition, в якому можна перевірити атрибут користувача і повернути свій власний RequestCondition.

Явна реєстрація

Ти можеш програмно реєструвати методи обробника, які можна використовувати для динамічної реєстрації або при більш складних випадках, наприклад, якщо є різні екземпляри одного і того ж обробника під різними URL-адресами. У цьому прикладі реєструється метод обробника:

Java
@Configuration
public class MyConfig {
    @Autowired
    public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) 
            throws NoSuchMethodException {
        RequestMappingInfo info = RequestMappingInfo
                .paths("/user/{id}").methods(RequestMethod.GET).build(); 
        Method method = UserHandler.class.getMethod("getUser", Long.class); 
        mapping.registerMapping(info, handler, method); 
    }
}
  1. Впроваджуємо цільовий обробник та відображення обробника для контролерів.
  2. Готуємо метадані відображення запиту.
  3. Отримуємо метод обробника.
  4. Додаємо реєстрацію.
Kotlin
@Configuration
class MyConfig {
    @Autowired
    fun setHandlerMapping(mapping: RequestMappingHandlerMapping, handler: UserHandler) { 
        val info = RequestMappingInfo.paths("/user/{id}").methods(RequestMethod.GET).build() 
        val method = UserHandler::class.java.getMethod("getUser", Long::class.java) 
        mapping.registerMapping(info, handler, method) 
    }
}
  1. Впроваджуємо цільовий обробник та відображення обробника для контролерів.
  2. Готуємо метадані відображення запиту.
  3. Отримуємо метод обробника.
  4. Додаємо реєстрацію.