Павел
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 На этом разрешите откланяться 🤓, до новых встреч…
Комментарии (11)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
testov QA Automation Engineer
14 апреля, 07:08
А существует возможность сделать header со значением на русском? Да, я могу сделать header.set("value", "значение"). Но ответ придет в виде "value" : "????????" Насколько я понял это связанно с тем что header кодируется в аски. Я находил методы кодирования названий файлов, но приспособить к моему случаю не смог.
Павел
Уровень 11, Россия
14 апреля, 13:22
Н встречал даже таких случаев что бы хэдеры были на кирилице, хэдер это техническая информация, она не для чтения широкой публикой, поэтому везде используется латиница.
Василий Бабин Мракоборец в Дурмстранг Expert
4 сентября 2021, 16:36
Инетресно то, что вот это некорректно работает (раньше по другим статьям тоже так было):
//поиск записи по 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);
}
Павел
Уровень 11, Россия
6 сентября 2021, 07:42
Спасибо! Я только что не могу найти где это в прошлой статье я писал? Хотя сам же на нее и ссылаюсь) Будет время поищу, наверное это в одной из внешних ссылок, пока в этой статье сделаю сноску на твой комментарий. В рамках примера твой костыль вполне нормальный, а если по правилам то нужно обработать эту ситуацию и выбросить исключение логики приложения, в слое сервисов.
Василий Бабин Мракоборец в Дурмстранг Expert
6 сентября 2021, 09:25
Да это было в ссылке конечно, и по ней мы риализовывали и делали как бы самостоятельно. Немного некорректно отметил, извини! Вот твоя статься: Spring — это не страшно. Контролируем свой REST. И там ссылка на эти три статьи: Обзор REST. Часть 1: что такое REST в третей как раз: Обзор REST. Часть 3: создание RESTful сервиса на Spring Boot
Василий Бабин Мракоборец в Дурмстранг Expert
6 сентября 2021, 09:27
Думаю там это корректно, по тому что они используют мапу как хранилище.
Василий Бабин Мракоборец в Дурмстранг Expert
7 сентября 2021, 15:13
Да, по факту мы должны обработать или прокинуть дальше исключение, и это происходит потому, что мы используем это выражение в сервисе (если не находим, то создаём новый экземпляр):
public PhoneBalanceDto findDtoById(Integer id) {
	return mappingUtil.mapToPhoneBalanceDto(
			phoneBalanceRepository.findById(id)
					.orElse(new PhoneBalanceEntity())
	);
}
Богдан
Уровень 29, Одесса
4 февраля 2021, 19:44
Вот здесь:
@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 должен быть
Павел
Уровень 11, Россия
5 февраля 2021, 10:41
Поправил, спасибо😇
Богдан
Уровень 29, Одесса
4 февраля 2021, 10:06
Спасибо за продолжение! Пара замечаний Вот здесь немножко "торчат уши" из проекта про овощи:
//поиск записи по 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);
Павел
Уровень 11, Россия
4 февраля 2021, 18:23
Благодарю за комментарий! "Уши" - обрезал) В
return ResponseEntity.ok().body(HttpStatus.OK);
хотел показать что есть такое .body()