В моем реальном сценарии у меня есть служба REST для целей AJAX. Он отображает ряд данных для графиков. Я хочу проверить это с отличным HttpBuilder Groovy . Однако существует проблема — эти запросы доступны только для уже вошедших в систему пользователей. В этом посте я представляю полное решение для поддержания состояния сеанса между запросами HttpBuilder .
Сессия в HttpBuilder
Прежде всего, быстрое напоминание о сессии. Сеанс — это симуляция состояния для HTTP-запросов, которые по своей природе не имеют состояния. После входа в систему вы получаете уникальный файл cookie (один или несколько), который идентифицирует вас для последовательных запросов. Каждый раз, когда вы отправляете запрос, вы отправляете этот cookie вместе. Таким образом, сервер распознает вас и сопоставляет с вашим сеансом, который хранится на сервере. Cookie-файл становится действительным, когда вы выходите из системы или время его истекает, например, после 20 минут бездействия. При следующем посещении страницы вы получите новый уникальный файл cookie.
Чтобы сохранить сеанс в HttpBuilder, мне нужно:
- войдите в мое приложение Grails
- получить cookie-файл JSESSIONID в ответ
- сохранить этот файл 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() { } 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
, но я предпочитаю, чтобы это был первый тест в спецификации. Второй тест всегда выполняется после входа в систему, поэтому он отправляет куки в заголовках запроса. Это именно то, чего я хотел достичь.