JavaRush /Java блог /Java Developer /Spring — это не страшно. Cookie & Headers
Павел
11 уровень

Spring — это не страшно. Cookie & Headers

Статья из группы Java Developer
СОДЕРЖАНИЕ ЦИКЛА СТАТЕЙ Повторение – мать учения! Поэтому, на основании прошлых статей, давайте создадим новый web spring-boot проект: MobilePhonePayment (Оплата мобильного телефона) Подключите h2, Lombok. Создайте слой сущностей: BalancePhoneEntity Integer id; Integer numberPhone; String nameCustomer; Integer balance; Создайте слой сервисов с методами: - Поиск всех записей в базе - Поиск записи по id - Поиск записи по номеру телефона - Поиск записи по имени пользователя (должен возвращать лист записей, имена могу и совпадать)

public List<BalanceEntity> findByNameCustomer(String nameCustomer){
    return balanceRepository.findAllByNameCustomer(nameCustomer);
}
- Добавление записи в базу - Удаление записи из базы по id - Бизнесовый метод: Пополнение баланса телефона – метод должен принимать номер телефона, сумму (тип Integer) и увеличивать баланс соответствующего номера на указанную сумму.

public void addingMoneyToBalance(Integer phoneNumber, Integer sum) {
    BalanceDto byPhoneNumber = findByPhoneNumber(phoneNumber);
    byPhoneNumber.setBalance(byPhoneNumber.getBalance() + sum);
    save(byPhoneNumber);//метод save() – добавление, реализован в сервисе
}
Не забудьте реализовать маппинг из DTO в Entity и обратно. Dto будет аналогичен Entity: BalancePhoneDto Integer id; Integer numberPhone; String nameCustomer; Integer balance; Создайте слой DTO, создайте класс InitiateUtils и наполните базу данными: id 1, numberPhone 555000, balance 100, customer Иван id 2, numberPhone 444000, balance 250, customer Марья id 3, numberPhone 111000, balance 60, customer Иван Создайте рест-контроллер, но не спешите наполнять его методами. Если следовать прошлой статье, то метод вывода всех записей должен был получиться примерно так (рекомендую сейчас посмотреть в комментарии к статье - конкретно на комментарий Василия Бабина ):

//поиск записи по id - старая версия
@GetMapping(value = "/find-phone/{id}")
public ResponseEntity<BalanceDto> findPhone(@PathVariable Integer id) {
    BalanceDto balanceDto = balanceService.findById(id);
    return balanceDto != null
            ? new ResponseEntity<>(balanceDto, HttpStatus.OK)
            : new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Есть другой способ использования ResponseEntity, без использования конструктора. Его мы и будем дальше использовать. ResponseEntity предоставляет два вложенных интерфейса компоновщика: HeadersBuilder и его подинтерфейс, BodyBuilder. Следовательно, мы можем получить доступ к их возможностям через статические методы ResponseEntity. Почитать по больше можно в этой статье. Методы рест-контроллера можно реализовать следующим образом: 1) Вывод записи по id

//поиск записи по id
@GetMapping(value = "/find-number-phoneById/{id}")
public ResponseEntity<?> findNumberPhoneById(@PathVariable Integer id) {
    BalanceDto balanceDto = balanceService.findById(id);
    return balanceDto != null
            ? ResponseEntity.ok(balanceDto)
            : ResponseEntity.ok().body(HttpStatus.NOT_FOUND);
}
Тестируем в Postman (О нем говорили в прошлой статье, вот еще небольшой гайд по этой программе). Запускаем, выбираем тип запроса GET, в строке URL пишем: http://localhost:8080/find-number-phoneById/1 – параметр id мы передали в строке запроса, на выводе получим запись с id равным 1. После добавления нового кода, не забываем перезапускать проект😇 2) Вывод записей по имени

//поиск записи по имени пользователя
@GetMapping(value = "/find-number-phoneByName/{name}")
public ResponseEntity<?> findNumberPhone(@PathVariable String name) {
    List<BalanceDto> balanceDto = balanceService.findByNameCustomer(name);
    return balanceDto != null &&  !balanceDto.isEmpty()
            ? ResponseEntity.ok(balanceDto)
            : ResponseEntity.ok().body(HttpStatus.NOT_FOUND);
}
Создадим новый запрос, выбираем тип запроса GET, в строке URL пишем: http://localhost:8080/ find-number-phoneByName/Иван – параметр name мы передали в строке запроса, на выводе получим лист записей с nameCustomer равным Иван. Возможно на выводе вы получите что то похожее на это: %D1%8D%D1%82%D0%BE%20%D0%BD%D0%B5%20%D0%BE%D1%88%D0%B8%D0%B1%D0%BA%D0%B0 Это не ошибка — это особенности кодировки запросов, почитайте об этом. А вот тут написано как настроить Postman что бы этого не происходило. 3) Вывод всех записей:

//поиск всех записей
@GetMapping(value = "/findAll")
public ResponseEntity<?> findAll() {
    List<BalanceDto> balanceDto = balanceService.findAll();
    return balanceDto != null &&  !balanceDto.isEmpty()
            ? ResponseEntity.ok(balanceDto)
            : ResponseEntity.ok().body(HttpStatus.NOT_FOUND);
}
Создаем новый запрос, выбираем тип запроса GET, в строке URL пишем: http://localhost:8080/findAll – параметров тут ни каких не передаем. 4) Добавления новой записи:

//добавление новой записи
@PostMapping(value = "/entry")
public ResponseEntity<?> entryNumber(@RequestBody BalanceDto dto){
    balanceService.save(dto);
    return ResponseEntity.ok().body(HttpStatus.CREATED);
}
Создаем новый запрос, выбираем тип запроса POST, в строке URL пишем: http://localhost:8080/entry. В этом запросе нам необходимо передать объект в формате JSON. В окне запроса переходим на вкладку Body, устанавливаем флаг на raw, рядом с Text нажимаем стрелочку и выбираем JSON. В окно копируем следующий JSON:

{
        "numberPhone": 767676,
        "nameCustomer": "Саша",
        "balance": 100
}
Нажимаем выполнить запрос в ответе приходит статус CREATED. Теперь сделайте еще раз запрос на findAll и убедитесь, что появилась новая запись. 5) Удаления записи по id

//удаление записи по id
@DeleteMapping(value = "/delete-phoneById/{id}")
public ResponseEntity<?> delete(@PathVariable Integer id) {
    balanceService.delete(id);
    return ResponseEntity.ok().body(HttpStatus.OK);
}
Создаем новый запрос, выбираем тип запроса DELETE, в строке URL пишем: http://localhost:8080/delete-phoneById/4 – параметр id мы передали в строке запроса, На выводе получим статус OK. Теперь сделайте еще раз запрос на findAll и убедитесь, что Саша пропал. 6) Изменение номера по id

//изменение номера телефона по id
@PutMapping(value = "/change")
public ResponseEntity<?> changeNumberPhone(
//можно добавлять несколько параметров в запрос
        @RequestParam(value = "id") Integer id, //добавили один параметр
        @RequestParam(value = "phoneNumber") Integer phoneNumber) //добавили второй параметр
 {
    BalanceDto byId = balanceService.findById(id);
    byId.setNumberPhone(phoneNumber);
    balanceService.save(byId);
    return ResponseEntity.ok().body(HttpStatus.OK);
}
Создаем новый запрос, выбираем тип запроса PUT, в строке URL пишем: http://localhost:8080/change . В этом запросе несколько параметров, и мы их как видите не передаем в строке запроса как раньше. Для параметров в методе используется аннотация @RequestParam. Что бы передать параметры через Postman, необходимо в окне запроса перейти на вкладку Params, в колонке Key указываем наименование параметра (id), в колонке Value указываем значение (1). Со вторым параметром поступаем так же, Key = phoneNumber, Value = 888000. Обратите внимание на строку запроса, Postman изменил ее что бы передать параметры правильно. На выводе получим статус OK. Теперь сделайте еще раз запрос на findAll и убедитесь, что номер телефона у первой записи изменился. 7) Пополнение баланса телефона

@PutMapping(value = "/add")
public ResponseEntity<?> addingMoney(
        //можно добавлять несколько параемров в запрос
        @RequestParam(value = "phoneNumber") Integer phoneNumber,//добавили один параметр
        @RequestParam(value = "sum") Integer sum) //добавили второй параметр
{
    balanceService.addingMoneyToBalance(phoneNumber, sum);
    return ResponseEntity.ok().body(HttpStatus.OK);
}
Создаем новый запрос, выбираем тип запроса PUT, в строке URL пишем: http://localhost:8080/add. Значение phoneNumber ставим равным 888000, sum равным 130. На выводе получим статус OK. Теперь сделайте еще раз запрос на findAll и убедитесь, что баланс у первой записи изменился. 8) PUT через тело запроса - так делать предпочтительнее, что бы не открывать передаваемые данные

@PutMapping(value = "/add")
public ResponseEntity<?> addingMoney(@RequestBody BalanceDto dto){
    balanceService.addingMoneyToBalance(dto.getPhoneNumber, dto.getSum);
    return ResponseEntity.ok().body(HttpStatus.OK);
}
Передаем JSON

{
        "numberPhone": 888000,
       //  "nameCustomer" можно вообще не указывать 
        "balance": 130
}
Наконец-то переходим к Cookie. Что такое Cookie. Просто говоря: Cookie хранят данные, полученные один раз браузером от приложения, которые потом можно многократно использовать на сайте. Нужно знать две базовые вещи: как записать и как прочитать Cookie. Как записать: Весь Spring Web MVC реализован поверх Servlet API, которое построено вокруг двух объектов — запрос от клиента оборачивается в HttpSerlvetRequest, а ответ формируется из заполненного вашим кодом HttpServletResponse. Имея доступ к этим объектам, вы получаете полный контроль над всей HTTP сессией. Spring web позволяет обращаться к этим объектам напрямую. Обычно Cookie, создадим в контроллере метод

//записать куки
 @GetMapping(value = "/set-cookie")
public ResponseEntity<?> setCookie(HttpServletResponse response) throws IOException {
     Cookie cookie = new Cookie("data", "Come_to_the_dark_side");//создаем объект Cookie,
     //в конструкторе указываем значения для name и value
     cookie.setPath("/");//устанавливаем путь
     cookie.setMaxAge(86400);//здесь устанавливается время жизни куки
     response.addCookie(cookie);//добавляем Cookie в запрос
     response.setContentType("text/plain");//устанавливаем контекст
     return ResponseEntity.ok().body(HttpStatus.OK);//получилось как бы два раза статус ответа установили, выбирайте какой вариант лучше
 }
Сделаем GET запрос в Postman по адресу: http://localhost:8080/set-cookie на выходе получим OK. Над океем найдите надпись Cookie(1), перейдя по ней вы увидите те Cookie что мы передали. Имя: data , значение: Come_to_the_dark_side. Информация по основным возможностям класса Cookie в java. Как прочитать: Прочитать еще легче

//прочитать куки
@GetMapping(value = "/get-cookie")
public ResponseEntity<?> readCookie(@CookieValue(value = "data") String data) {
    return ResponseEntity.ok().body(data);
}
В @ CookieValue указываем имя Cookie, значение которых будем считывать, и выводим прочитанное значение в ответе. Come_to_the_dark_side Теперь настал звездный час Header (заголовков, не смотрите что статья про PHP), почитать довольно полезно ): Для начала посмотрим, как можно прочитать заголовки:

//прочитать заголовки
@GetMapping(value = "/get-headers")
public ResponseEntity<?> getHeaders(@RequestHeader Map<String, String> headers){//представляет заголовки ввиде мапы,
    //где ключ это наименование заголовка, а значение мапы - это значение заголовка
    return ResponseEntity.ok(headers);
}
Основную работу берет на себя @RequestHeader Map<String, String>, он представляет заголовки в виде мапы, где ключ это наименование заголовка, а значение мапы - это значение заголовка. Тестировать этот метод интересней с помощью браузера, открываем браузер, набираем в поисковой строке http://localhost:8080/get-headers, на выходе получаем обширный список заголовков. Погуглите про каждый заголовок, чтобы понять зачем они нужны. Википедия тоже предлагает список заголовков. «Если кто-то что-то прочитал, значит это кто-то записал» - старинная программистская поговорка. Давайте запишем заголовок

//записать заголовок
@GetMapping(value = "/set-header")
public ResponseEntity<?> setHeader(){
    return ResponseEntity.ok().header("name-header","value-header").body(HttpStatus.OK);
}
Здесь мы использовали специальный метод header класса ResponseEntity. Где "name-header" – это наименование заголовка, а "value-header" – это значение заголовка. Есть и другие варианты работы с заголовками

//еще варианты работы с заголовками
@GetMapping(value = "/set-headers")
public ResponseEntity<?> setHeaders() {
    HttpHeaders httpHeaders = new HttpHeaders();//создаем объект
    //который имплементирует мапу MultiValueMap<String, String>
    //наполняем ее парами ключ-значение
    //можно наполнить своими заголовками через метод add
    httpHeaders.add("customer-header", "value-header1");
    //HttpHeaders так же предлагает большой выбор стандартных заголовков
    //Посмотрите на них набрав в IDEA HttpHeaders.
    httpHeaders.add(HttpHeaders.FROM, "russia");
    //можно изменить существующий заголовок, вызвав для него сет-метод
    httpHeaders.setDate(0);
    //или получить значение конкретного заголовка
    Long date = httpHeaders.getDate();
    System.out.println(date);
    return ResponseEntity
            .ok().headers(httpHeaders)//здесь метод принимающий MultiValueMap<String, String>
            .body(HttpStatus.OK);
}
Здесь используется уже другой метод класса ResponseEntity, который принимает значение типа MultiValueMap<String, String>. Проверить как это работает, тоже информативнее будет в браузере. Переходим по адресу http://localhost:8080/set-headers, получаем ответ, что статус ОК. Если вы молодец и используете гугл хром, то жмите сочетание клавиш Ctrl + Shift + I и переходите в «Инструменты разработчика», далее ищите в верхней панели вкладку Network, нажав ее ищите запись: set-headers (если ее нет обновите страницу) нажимаем на нее и в открывшемся окне выбираем вкладку Headers и в ResponseHeaders видим наши заголовки. Для знакомства с заголовками, пока достаточно. А теперь прочтите: Изучаем ResponseEntity<!--?--> и избавляемся от него в контроллерах Spring На этом разрешите откланяться 🤓, до новых встреч…
Комментарии (15)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Илья Барабанов Уровень 1
10 июня 2023
Делаем JWT авторизацию, но рефреш токен почему то не записывается в куки, точнее в запросе он записывается, показывается сам токен, флаг http only true, cors заголовки установлены, но в самом браузере для localhost куки не записывается, пробовали с купленного домена, та же тема. После авторизации, когда пользователь на странице профиля, куки не пишутся в браузере, но в запросе они установлены

Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken);
refreshTokenCookie.setMaxAge(7 * 24 * 60 * 60);
refreshTokenCookie.setHttpOnly(true);
refreshTokenCookie.setPath("/");
response.addCookie(refreshTokenCookie);

response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpServletResponse.SC_OK);
System.out.println(response);
//response.getWriter().write(new ObjectMapper().writeValueAsString(new AuthenticationResponse(accessToken)));
//response.getWriter().flush();
System.out.println("Отправляем токены");
//response.send
return ResponseEntity.ok(new AuthenticationResponse(accessToken));
}
testov Уровень 30
14 апреля 2022
А существует возможность сделать header со значением на русском? Да, я могу сделать header.set("value", "значение"). Но ответ придет в виде "value" : "????????" Насколько я понял это связанно с тем что header кодируется в аски. Я находил методы кодирования названий файлов, но приспособить к моему случаю не смог.
Василий Бабин Уровень 28 Expert
4 сентября 2021
Инетресно то, что вот это некорректно работает (раньше по другим статьям тоже так было):

//поиск записи по id
@GetMapping(value = "/find-number-phoneById/{id}")
public ResponseEntity<?> findNumberPhoneById(@PathVariable Integer id) {
    BalanceDto balanceDto = balanceService.findById(id);
    return balanceDto != null
            ? ResponseEntity.ok(balanceDto)
            : ResponseEntity.ok().body(HttpStatus.NOT_FOUND);
}
Если ввести 4, то возращается статус ОК и JSON:

{
    "id": null,
    "numberPhone": null,
    "nameCustomer": null,
    "balance": null
}
То есть balanceDto != null всегда true. Поэтому я переделала так (конечно костыль, но тогда срабатывает):

	@GetMapping("/find-number-phoneById/{id}")
	public ResponseEntity<?> findPhoneNumberById(@PathVariable Integer id) {
		PhoneBalanceDto balanceDto = phoneBalanceService.findDtoById(id);

		if (balanceDto.getId() == null) {
			return ResponseEntity.ok().body(HttpStatus.NOT_FOUND);
		}

		return ResponseEntity.ok(balanceDto);
	}
Богдан Уровень 29
4 февраля 2021
Вот здесь:

 @GetMapping(value = "/ set-cookie")
в маппинге наверное стоит удалить пробел (хоть и не критично). А вот тут: Сделаем GET запрос в Postman по адресу: http://localhost:8080/cookie на выходе получим OK. Над океем найдите надпись Cookie(1), перейдя по ней вы увидите те Cookie что мы передали. Имя: data , значение: Come_to_the_dark_side. запрос все таки на http://localhost:8080/ set-cookie должен быть
Богдан Уровень 29
4 февраля 2021
Спасибо за продолжение! Пара замечаний Вот здесь немножко "торчат уши" из проекта про овощи:

//поиск записи по id - старая версия
@GetMapping(value = "/find-phone/{id}")
public ResponseEntity<BalanceDto> findPhone(@PathVariable Integer id) {
    BalanceDto productDto = balanceService.findById(id);
    return productDto != null
            ? new ResponseEntity<>(productDto, HttpStatus.OK)
            : new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Copy-paste в действии))) 2. Статический метод ok класса ResponseEntity принимает body, так что вполне можно и так (хотя может и не так же наглядно):

// вместо этого
return ResponseEntity.ok().body(HttpStatus.OK);
// можно вот это
return ResponseEntity.ok(HttpStatus.OK);