Ти можеш використовувати анотацію @RequestMapping
для відображення запитів на методи контролерів.
Вона має різні атрибути для зіставлення за URL-адресою, HTTP-методом, параметрами запиту, заголовками та
типами даних. ЇЇ можна використовувати на рівні класу для вираження загальних відображень або на рівні методу
для звуження до конкретного відображення кінцевої точки. Існують також специфічні для HTTP-методу варіанти
скорочення анотації @RequestMapping
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
Скорочення є кастомними анотаціями, які вказуються тому, що,
мабуть, більшість методів контролера потрібно зіставити з конкретним HTTP-методом, а не використовувати анотацію
@RequestMapping
, яка за замовчуванням здійснює зіставлення з усіма HTTP-методами. Для вираження
загальних
відображень на рівні класу, як і раніше, необхідна @RequestMapping
.
Наступний приклад має відображення на рівні типів та методів:
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
@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
. Наприклад:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
// ...
}
Ти можеш оголошувати змінні URI-ідентифікаторів на рівні класів та методів, як показано в наступному прикладі:
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
@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"
, то наступний
метод отримує ім'я, версію та розширення файлу:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
// ...
}
@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
запиту, як показано в
наступному прикладі:
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet( @RequestBody Pet pet) {
// ...
}
- Використання атрибуту
consumes
для звуження діапазону відображення за типом вмісту.
@PostMapping("/pets", consumes = ["application/json" ])
fun addPet(@RequestBody pet: Pet) {
// ...
}}
- Використання атрибуту
consumes
для звуження діапазону відображення за типом вмісту.
Атрибут consumes
також підтримує вирази заперечення. Наприклад, !text/plain
означає
будь-який тип вмісту, відмінний від text/plain
.
Можна оголошувати загальний атрибут consumes
на рівні класу. Однак, на відміну від більшості інших атрибутів відображення запитів, при використанні на рівні
класу атрибут consumes
на рівні методу радше перевизначає, а не розширює оголошення на рівні класу.
MediaType
надає константи для часто використовуваних типів
середовищ передачі даних, таких як APPLICATION_JSON_VALUE
та APPLICATION_XML_VALUE
.
Типи даних, що отримуються
Можна звузити діапазон відображення запитів на основі заголовка запиту Accept
та списку типів вмісту, які виробляє метод контролера, як показано в наступному прикладі:
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
- Використання атрибуту
produces
для звуження діапазону відображення за типом вмісту.
@GetMapping("/pets/{petId}", produces = ["application/json"])
@ResponseBody
fun getPet( @PathVariable petId: String): Pet {
// ...
}
- Використання атрибуту
produces
для звуження діапазону відображення типу вмісту.
Тип середовища передачі даних може визначати набір символів. Підтримуються заперечні вирази — наприклад,
!text/plain
означає будь-який тип вмісту, відмінний від "text/plain".
Можна оголошувати
загальний атрибут produces
на рівні класу. Однак, на відміну від більшості інших атрибутів відображення
запитів, при використанні на рівні класу атрибут produces
на рівні методу швидше перевизначає, а не
розширює оголошення на рівні класу.
MediaType
надає константи для часто використовуваних типів
середовищ передачі даних, таких як APPLICATION_JSON_VALUE
та APPLICATION_XML_VALUE
.
Параметри, заголовки
Можна звузити діапазон відображення запитів на основі умов параметрів запиту. Ти
маєш змогу перевірити наявність параметра (myParam
), його відсутність (!myParam
) або певне
значення(myParam=myValue
). У цьому прикладі показано, як перевірити наявність певного значення:
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue ")
public void findPet(@PathVariable String petId) {
// ...
}
- Перевірка того, чи дорівнює
myParam
значеннюmyValue
.
@GetMapping("/pets/{petId}", params = ["myParam=myValue"])
fun findPet(@PathVariable petId: String) {
// ...
}
- Перевірка того, чи дорівнює
myParam
значеннюmyValue
.
Те саме можна використовувати і з умовами заголовка запиту, як показано в наступному прикладі:
@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
- Перевір, чи дорівнює
myHeader
значенняmyValue
.
@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-адресами. У цьому прикладі реєструється метод обробника:
@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);
}
}
- Впроваджуємо цільовий обробник та відображення обробника для контролерів.
- Готуємо метадані відображення запиту.
- Отримуємо метод обробника.
- Додаємо реєстрацію.
@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)
}
}
- Впроваджуємо цільовий обробник та відображення обробника для контролерів.
- Готуємо метадані відображення запиту.
- Отримуємо метод обробника.
- Додаємо реєстрацію.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ