Статьи

Как сохранить сессию в HttpBuilder с куки

В моем реальном сценарии у меня есть служба REST для целей AJAX. Он отображает ряд данных для графиков. Я хочу проверить это с отличным HttpBuilder Groovy . Однако существует проблема — эти запросы доступны только для уже вошедших в систему пользователей. В этом посте я представляю полное решение для поддержания состояния сеанса между запросами HttpBuilder .

Сессия в HttpBuilder

Прежде всего, быстрое напоминание о сессии. Сеанс — это симуляция состояния для HTTP-запросов, которые по своей природе не имеют состояния. После входа в систему вы получаете уникальный файл cookie (один или несколько), который идентифицирует вас для последовательных запросов. Каждый раз, когда вы отправляете запрос, вы отправляете этот cookie вместе. Таким образом, сервер распознает вас и сопоставляет с вашим сеансом, который хранится на сервере. Cookie-файл становится действительным, когда вы выходите из системы или время его истекает, например, после 20 минут бездействия. При следующем посещении страницы вы получите новый уникальный файл cookie.

Чтобы сохранить сеанс в HttpBuilder, мне нужно:

  1. войдите в мое приложение Grails
  2. получить cookie-файл JSESSIONID в ответ
  3. сохранить этот файл cookie и отправить его вместе с каждым последующим запросом

Я создал класс RestConnector который упаковывает HttpBuilder. Его главное улучшение заключается в том, что он сохраняет полученный файл cookie в списке.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package eu.spoonman.connectors.RestConnector
 
import groovyx.net.http.Method
import groovyx.net.http.ContentType
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.HttpResponseDecorator
 
class RestConnector {
    private String baseUrl
    private HTTPBuilder httpBuilder
    private List<String> cookies
 
    RestConnector(String url) {
        this.baseUrl = url
        this.httpBuilder = initializeHttpBuilder()
        this.cookies = []
    }
 
    public def request(Method method, ContentType contentType, String url, Map<String, Serializable> params) {
        debug("Send $method request to ${this.baseUrl}$url: $params")
        httpBuilder.request(method, contentType) { request ->
            uri.path = url
            uri.query = params
            headers['Cookie'] = cookies.join(';')
        }
    }
 
    private HTTPBuilder initializeHttpBuilder() {
        def httpBuilder = new HTTPBuilder(baseUrl)
 
        httpBuilder.handler.success = { HttpResponseDecorator resp, reader ->
            resp.getHeaders('Set-Cookie').each {
                //[Set-Cookie: JSESSIONID=E68D4799D4D6282F0348FDB7E8B88AE9; Path=/frontoffice/; HttpOnly]
                String cookie = it.value.split(';')[0]
                debug("Adding cookie to collection: $cookie")
                cookies.add(cookie)
            }
            debug("Response: ${reader}")
            return reader
        }
        return httpBuilder
    }
 
    private debug(String message) {
        System.out.println(message) //for Gradle
    }
}

Несколько вещей, чтобы заметить в классе выше. Конструктор устанавливает базовый URL и создает экземпляр HttpBuilder, который можно использовать повторно. Далее, есть обработчик успешного запроса, который проверяет, получаю ли я какой-либо файл cookie. Добавляет полученные куки в список. Наконец, есть метод request который вызывает HttpBuilder#request но он добавляет куки в заголовки HTTP, чтобы сервер мог распознать меня как вошедшего в систему пользователя.

Отправка куки с каждым запросом является ключевым компонентом здесь. Он имитирует поведение браузера и поддерживает сессию.

Как это использовать?

Ниже я покажу вам, как использовать этот служебный класс в тесте Спока. Это довольно просто.

Сначала я захожу в свое приложение и гарантирую, что получаю взамен cookie, что эквивалентно входу в систему. Затем я отправляю запрос с этим cookie, отправленный в заголовке HTTP. Это тест Спока, который его реализует:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package eu.spoonman.specs.rest
 
import eu.spoonman.connectors.RestConnector.RestConnector
import groovyx.net.http.ContentType
import groovyx.net.http.Method
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Stepwise
 
@Stepwise
class RestChartSpec extends Specification {
    @Shared
    RestConnector restConnector
 
    def setupSpec() {
        restConnector = new RestConnector('http://localhost:8080')
    }
 
    def "should login as test"() {
        given:
            Map params = [j_username: 'test', j_password: 'test']
        when:
            restConnector.request(Method.POST, ContentType.ANY, '/frontoffice/j_spring_security_check', params)
        then:
            !(restConnector.cookies.empty)
    }
 
    def "should allow access to chart data series"() {
        given:
            Map params = [days: 14]
        when:
            Map result = restConnector.request(Method.POST, ContentType.JSON, "frontoffice/chart/series", params)
        then:
            result != null
            result.series.size() > 0
    }
}

Я создаю новый экземпляр RestConnector в setupSpec с базовым URL моего приложения. Обратите внимание, что у него есть аннотация @Shared поэтому она распределяется между тестами.

@Stepwise — критическая аннотация для этой спецификации. Это означает, что Спок выполняет тесты именно в том порядке, в котором они определены. Мне нужно убедиться, что логин выполняется первым. Мне также нужно утверждать, что я получаю печенье и список не пуст. Я мог бы также перенести этот шаг в метод setupSpec , но я предпочитаю, чтобы это был первый тест в спецификации. Второй тест всегда выполняется после входа в систему, поэтому он отправляет куки в заголовках запроса. Это именно то, чего я хотел достичь.

Ссылка: Как сохранить сеанс в HttpBuilder с помощью файлов cookie от нашего партнера JCG Томаша Калкосинского в блоге рефактора .