Статьи

Java: будьте уверены (или отдыхайте очень легко)

Недавно мне пришлось написать некоторый Java-код для использования сервисов REST через HTTP. Я решил использовать клиентские библиотеки RestEasy , фреймворка, который я использую большую часть времени для предоставления сервисов REST в Java, поскольку он также реализует официальную спецификацию JAX-RS . Я очень доволен подходом, основанным на аннотациях, который определяется в спецификации, и он делает доступ к сервисам REST очень приятной задачей. Но, к сожалению, я не могу сказать, что мне нравится клиентский API таким же образом. Если вам повезло, что вы можете создать прокси-клиент на основе интерфейса, реализованного службой, это неплохо:

1
2
3
4
5
6
7
import org.jboss.resteasy.client.ProxyFactory;
...
// this initialization only needs to be done once per VM
RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
 
SimpleClient client = ProxyFactory.create(MyRestServiceInterface.class, 'http://localhost:8081');
client.myBusinessMethod('hello world');

Я согласен, что иметь Proxy-клиент, похожий на JAX-WS, хорошо. Но в большинстве случаев, когда мы используем веб-сервис REST, у нас нет Java-интерфейса для импорта. Все те Twitter, Google или любые другие доступные общественные службы отдыха — это всего лишь конечные точки HTTP. В этих случаях можно использовать RestEasy, полагаясь на API клиентского запроса RestEasy:

01
02
03
04
05
06
07
08
09
10
11
12
13
ClientRequest request = new ClientRequest('http://localhost:8080/some/path');
request.header('custom-header', 'value');
 
// We're posting XML and a JAXB object
request.body('application/xml', someJaxb);
 
// we're expecting a String back
ClientResponse<String> response = request.post(String.class);
 
if (response.getStatus() == 200) // OK!
{
   String str = response.getEntity();
}

На мой взгляд, это очень многословный способ получить то, что чаще всего, просто куча строк из Интернета. И это становится еще хуже, если вам нужно включить информацию аутентификации:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
// Configure HttpClient to authenticate preemptively
// by prepopulating the authentication data cache.
 
// 1. Create AuthCache instance
AuthCache authCache = new BasicAuthCache();
 
// 2. Generate BASIC scheme object and add it to the local auth cache
BasicScheme basicAuth = new BasicScheme();
authCache.put('com.bluemonkeydiamond.sippycups', basicAuth);
 
// 3. Add AuthCache to the execution context
BasicHttpContext localContext = new BasicHttpContext();
localContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
 
// 4. Create client executor and proxy
httpClient = new DefaultHttpClient();
ApacheHttpClient4Executor executor = new ApacheHttpClient4Executor(httpClient, localContext);
client = ProxyFactory.create(BookStoreService.class, url, executor);

Я обнаружил, что Rest-assured предоставляют гораздо более приятный API для написания клиентских вызовов. Официально цель проекта — создать систему тестирования и валидации ; и большинство учебных пособий, посвященных этим аспектам, например, недавнее руководство Хайко Раппа: http://pilhuhn.blogspot.nl/2013/01/testing-rest-apis-with-rest-assured.html . Вместо этого я предлагаю использовать его как инструмент разработки, чтобы очень быстро экспериментировать и писать вызовы REST. Что важно знать об отдыхе:

  • он реализует специфичный для домена язык благодаря плавному API
  • это одна зависимость Maven
  • он почти полностью раскрывает общий стиль для объектов ответов xml и json
  • это зависит от Apache Commons Client

Итак, я покажу вам несколько реальных примеров использования и оставлю вам хорошую ссылку, если вы хотите узнать больше. Как и большинство DSL на Java, он работает лучше, если статически импортировать наиболее важные объекты :

1
2
import static   com.jayway.restassured.RestAssured.*;
import static   com.jayway.restassured.matcher.RestAssuredMatchers.*;

Базовое использование:

Это возвращает:

1
2
3
<errors>
  <error code="34">Sorry, that page does not exist</error>
</errors>

Ой, какая-то ошибка . Да, нам нужно передать некоторый параметр:

1
2
3
with()
    .parameter('screen_name', 'resteasy')

Это возвращает:

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
<user>
  <id>27016395</id>
  <name>Resteasy</name>
  <screen_name>resteasy</screen_name>
  <location></location>
  <profile_image_url_https>https://si0.twimg.com/sticky/default_profile_images/default_profile_0_normal.png</profile_image_url_https>
  <url></url>
  <description>jboss.org/resteasy
  
JBoss/Red Hat REST project</description>
  <protected>false</protected>
  <followers_count>244</followers_count>
  <profile_background_color>C0DEED</profile_background_color>
  <profile_text_color>333333</profile_text_color>
  <profile_link_color>0084B4</profile_link_color>
  <profile_sidebar_fill_color>DDEEF6</profile_sidebar_fill_color>
  <profile_sidebar_border_color>C0DEED</profile_sidebar_border_color>
  <friends_count>1</friends_count>
  <created_at>Fri Mar 27 14:39:52 +0000 2009</created_at>
  <favourites_count>0</favourites_count>
  <utc_offset></utc_offset>
  <time_zone></time_zone>
  <profile_background_image_url>http://a0.twimg.com/images/themes/theme1/bg.png</profile_background_image_url>
  <profile_background_image_url_https>https://si0.twimg.com/images/themes/theme1/bg.png</profile_background_image_url_https>
  <profile_background_tile>false</profile_background_tile>
  <profile_use_background_image>true</profile_use_background_image>
  <geo_enabled>false</geo_enabled>
  <verified>false</verified>
  <statuses_count>8</statuses_count>
  <lang>en</lang>
  <contributors_enabled>false</contributors_enabled>
  <is_translator>false</is_translator>
  <listed_count>21</listed_count>
  <default_profile>true</default_profile>
  <default_profile_image>true</default_profile_image>
...
</user>

Намного лучше! Теперь предположим, что нам нужен только токен этого большого XML-кода String :

1
2
3
4
with()
    .parameter('screen_name', 'resteasy')
    .path('user.profile_image_url')

И вот наш вывод:

Что если это был ответ JSON ?

1
2
3
with()
    .parameter('screen_name', 'resteasy')

И вот наш вывод:

{"id":27016395,"id_str":"27016395","name":"Resteasy","screen_name":"resteasy","location":"","url":null,"description":"jboss.org\/resteasy\n\nJBoss\/Red Hat REST project","protected":false,"followers_count":244,"friends_count":1,"listed_count":21,"created_at":"Fri Mar 27 14:39:52 +0000 2009","favourites_count":0,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":8,"lang":"en","status":{"created_at":"Tue Mar 23 14:48:51 +0000 2010","id":10928528312,"id_str":"10928528312","text":"Doing free webinar tomorrow on REST, JAX-RS, RESTEasy, and REST-*. Only 40 min, so its brief. http:\/\/tinyurl.com\/yz6xwek","source":"web","truncated":false,"in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":0,"favorited":false,"retweeted":false},"contributors_enabled":false,"is_translator":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/a0.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/si0.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/a0.twimg.com\/sticky\/default_profile_images\/default_profile_0_normal.png","profile_image_url_https":"https:\/\/si0.twimg.com\/sticky\/default_profile_images\/default_profile_0_normal.png","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"default_profile":true,"default_profile_image":true,"following":null,"follow_request_sent":null,"notifications":null} 

И тот же интерфейс понимает навигацию объекта JSON. Обратите внимание, что выражение навигации не включает ‘user’, поскольку его не было в полном ответе json:

1
2
3
4
with()
    .parameter('screen_name', 'resteasy')
    .path('profile_image_url')

И вот наш вывод:

Теперь пример параметров пути :

1
2
3
with()
    .parameter('key', 'HomoSapiens')
.get('http://eol.org/api/search/{key}').asString()

Информация о http запросе :

Пример базовой аутентификации :

1
2
3
4
with()
  .auth().basic('paolo', 'xxxx')
  .statusLine()

Пример загрузки формы из нескольких частей

1
2
3
with()
    .multiPart('file', 'test.txt', fileContent.getBytes())
.post('/upload')

Maven зависимость :

1
2
3
4
5
6
<dependency>
 <groupid>com.jayway.restassured</groupid>
 <artifactid>rest-assured</artifactid>
 <version>1.4</version>
 <scope>test</scope>
</dependency>

А фрагмент Groovy, который можно вставить и выполнить непосредственно в groovyConsole благодаря Grapes, извлекает зависимости и автоматически добавляет их в путь к классам, что показывает поддержку JAXB :

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
@Grapes([ 
    @Grab('com.jayway.restassured:rest-assured:1.7.2')
])
import static   com.jayway.restassured.RestAssured.*
import static   com.jayway.restassured.matcher.RestAssuredMatchers.*
import  javax.xml.bind.annotation.*
 
@XmlRootElement(name = 'user')
@XmlAccessorType( XmlAccessType.FIELD )
    class TwitterUser {
        String id;
        String name;
        String description;
        String location;
 
        @Override
        String toString() {
            return 'Id: $id, Name: $name, Description: $description, Location: $location'
        }
 
    }
 
println with().parameter('screen_name', 'resteasy').get('http://api.twitter.com/1/users/show.xml').as(TwitterUser.class)
 
//

Это лишь краткий список возможностей библиотеки, просто вы представляете, как легко с ней работать. Для дальнейших примеров я предлагаю вам прочитать официальные страницы здесь: https://code.google.com/p/rest-assured/wiki/Usage . Или другой хороший учебник с примером приложения для игры: http://www.hascode.com/2011/10/testing-restful-web-services-made-easy-using-the-rest-assured-framework

Ссылка: Java: будьте уверены (или отдохните очень легко) от нашего партнера JCG Паоло Антинори в блоге Someday Never Comes .