Java rest api server example

Реализуем RESTful Web Service на java

Поводом к написанию статьи послужило, то что к моему большому удивлению на хабре я не нашёл статьи о реализации RESTful Web Service на Java, может, конечно, плохо искал. Да написано про RESTful web services очень много, но как то вот так, чтобы простенько с примерами кода, рабочий сервис, не так уж и легко найти и не только на хабре…

Вообще с REST я познакомился совсем недавно, не больше месяца назад. Так что буду очень благодарен за советы, поправки и критику!

Разобраться было и так вообщем то не сложно, но я думаю аналогичный пост мне бы очень помог и сильно бы ускорил процесс обучения! Тем более, если вы начинающий разработчик и о многом только слышали, а руками никогда не трогали.

По моему первому впечатлению: действительно вещь очень удобная, а главное очень простая, ещё и если использовать JSON, а не XML, ну по крайней мере мне так показалось после опыта работы с SOAP и WSDL. Ну, да об этом я думаю и так все знают, кто хоть немного работал с веб сервисами.

Так что, кто заинтересовался реализацией, прошу под кат

Сразу оговоримся:

1. весь код, конечно, в статье не выложишь и о нём не расскажешь;
2. версия проектика, конечно, не финальная и, как я уже говорил выше — буду очень благодарен за замечания и советы;
3. конечно же, есть баги.

  • JDK 1.6
  • Apache_CXF
  • Spring 3 Framework JDBC
  • Apache Tomcat 7.0
  • MySQL 5.1
  • Eclipse 4.2 Juno
  • Maven 3.0

ПО выбиралось по очень простому принципу — чем проще, тем лучше. Да, вместо MySQL для нагруженных сервисов без необходимости делать сложные запросы в базу, очень хорошо использовать MongoDB, ну, по крайней мере по этому поводу много написанно, да и опять же удобнее её использовать так как на входе тот же JSON.

Читайте также:  Javascript setinterval with this

2. В принципе что будет делать наш сервис — тут всё очень банально: сервис будет работать с одной табличкой в БД — сосбственно вставлять, апдейтать, удалять, ну и, конечно же, получать записи списком или по Id. Конечно же, хотелось бы иметь возможность параметризированного запроса на получение списка записей, не плохо было бы сделать «красивый» урл к сервису, прикрутить какой-нибудь интерсептор, чтобы, например, проверять права пользователя на доступ к сервису, или что-нибудь другое делать перед запуском сервиса, ну и как-то централизованно управлять кодами ошибок в ответах от сервера.

CREATE TABLE `customer` ( `id` varchar(45) NOT NULL, `first_name` varchar(45) DEFAULT NULL, `last_name` varchar(45) DEFAULT NULL, `phone` varchar(45) DEFAULT NULL, `mail` varchar(45) DEFAULT NULL, `adress` varchar(45) DEFAULT NULL, `contract_id` varchar(45) DEFAULT NULL, `contract_expire_date` date DEFAULT NULL ) 
1. http://mysite.com/service/customer

2. http://mysite.com/service/customer/

4 стандартных статуса, которые мы будем дополнительно обрабатывать (например, добавлять версию наших веб сервисов в ответ и при ошибке — наш код ошибки):

200 — Successful;
401 — Not Authorized;
404 — Not Found;
500 — Server error during operation.

3. Реализация (код на гитхабе тут):

Да, код, минимально комментировал, описание аннотаций тут.

public class CustomersServiceJSON implements ICustomersService < // link to our dao object private ICustomersDAO customersDAO; // for customersDAO bean property injection public ICustomersDAO getCustomersDAO() < return customersDAO; >public void setCustomersDAO(ICustomersDAO customersDAO) < this.customersDAO = customersDAO; >// for retrieving request headers from context // an injectable interface that provides access to HTTP header information. @Context private HttpHeaders requestHeaders; private String getHeaderVersion() < return requestHeaders.getRequestHeader("version").get(0); >// get by id service @GET @Path("/") public Response getCustomer(@PathParam("id") String id) < Customer customer = customersDAO.getCustomer(id); if (customer != null) < return ResponseCreator.success(getHeaderVersion(), customer); >else < return ResponseCreator.error(404, Error.NOT_FOUND.getCode(), getHeaderVersion()); >> // remove row from the customers table according with passed id and returned // status message in body @DELETE @Path("/") public Response removeCustomer(@PathParam("id") String id) < if (customersDAO.removeCustomer(id)) < return ResponseCreator.success(getHeaderVersion(), "removed"); >else < return ResponseCreator.success(getHeaderVersion(), "no such id"); >> // create row representing customer and returns created customer as // object->JSON structure @POST @Consumes(MediaType.APPLICATION_JSON) public Response createCustomer(Customer customer) < System.out.println("POST"); Customer creCustomer = customersDAO.createCustomer(customer); if (creCustomer != null) < return ResponseCreator.success(getHeaderVersion(), creCustomer); >else < return ResponseCreator.error(500, Error.SERVER_ERROR.getCode(), getHeaderVersion()); >> // update row and return previous version of row representing customer as // object->JSON structure @PUT @Consumes(MediaType.APPLICATION_JSON) public Response updateCustomer(Customer customer) < Customer updCustomer = customersDAO.updateCustomer(customer); if (updCustomer != null) < return ResponseCreator.success(getHeaderVersion(), updCustomer); >else < return ResponseCreator.error(500, Error.SERVER_ERROR.getCode(), getHeaderVersion()); >> // returns list of customers meeting query params @GET //@Produces(MediaType.APPLICATION_JSON) public Response getCustomers(@QueryParam("keyword") String keyword, @QueryParam("orderby") String orderBy, @QueryParam("order") String order, @QueryParam("pagenum") Integer pageNum, @QueryParam("pagesize") Integer pageSize) < CustomerListParameters parameters = new CustomerListParameters(); parameters.setKeyword(keyword); parameters.setPageNum(pageNum); parameters.setPageSize(pageSize); parameters.setOrderBy(orderBy); parameters.setOrder(Order.fromString(order)); ListlistCust = customersDAO.getCustomersList(parameters); if (listCust != null) < GenericEntity entity = new GenericEntity( listCust) < >; return ResponseCreator.success(getHeaderVersion(), entity); > else < return ResponseCreator.error(404, Error.NOT_FOUND.getCode(), getHeaderVersion()); >> > 

Всё достаточно просто — 4 веб сервиса в зависимости от URI и метода которым этот URI дёргается, есть объект DAO, который подключается в beans.xml и доступ к заголовкам запроса, чтобы доставать для примера кастомный заголовок «version».

Штука, которая отрабатывает перед тем как вызывается сервис:

public class PreInvokeHandler implements RequestHandler < // just for test int count = 0; private boolean validate(String ss_id) < // just for test // needs to implement count++; System.out.println("SessionID: " + ss_id); if (count == 1) < return false; >else < return true; >> public Response handleRequest(Message message, ClassResourceInfo arg1) < Map> headers = CastUtils.cast((Map) message .get(Message.PROTOCOL_HEADERS)); if (headers.get("ss_id") != null && validate(headers.get("ss_id").get(0))) < // let request to continue return null; >else < // authentication failed, request the authentication, add the realm return ResponseCreator.error(401, Error.NOT_AUTHORIZED.getCode(), headers.get("version").get(0)); >> > 

Здесь в методе validate() можно проверять какие-то пред условия, чисто для теста добавлена проверка кастомного заголовка в запросе идентификатор сессии «ss_id», ну, и с первого раза даже с этим заголовком будет падать 401.

Общий обработчик exceptions:

public class CustomExceptionMapper implements ExceptionMapper  < @Context private HttpHeaders requestHeaders; private String getHeaderVersion() < return requestHeaders.getRequestHeader("version").get(0); >public Response toResponse(Exception ex) < System.out.println(ex.getMessage() + ex.getCause()); return ResponseCreator.error(500, Error.SERVER_ERROR.getCode(), getHeaderVersion()); >> 

Что-то уже многовато кода для поста, есть ещё вспомогательный класс для формирования ответа серверу и глобальный enum для хранения наших кодов ошибок. Да, дескриптор развёртывания и beans.xml всё таки приведу тут:

. service contextConfigLocation WEB-INF/beans.xml   org.springframework.web.context.ContextLoaderListener  CXFServlet CXF Servlet org.apache.cxf.transport.servlet.CXFServlet 1  CXFServlet /*   

Тут основной интерес представляет подключение экшн сервлета от Apache — CXFServlet и стандартного спрингового ContextLoaderListener.

Здесь, собственно, задали нужные конфигурационные файлики для CXF, подключили DAO объект, наш предобработчик и обработчик исключительных ситуаций, конечно же, сам бин с сервисами и задали корень для сервисов.

Для того чтобы подёргать сервисы я использовал REST Console 4.0.2 плагин для хрома — штука достаточно простая, главное задать нужные ендпоинт, кастомные заголовки (как я уже говорил без «ss_id» всегда будет падать 401) и контент тип. Для примера:

Request Url: http://localhost:8080/service/customer Request Method: GET Status Code: 200 
Accept: application/json Content-Type: application/json ss_id: 12312.111 version: 12312.111 . 
Status Code: 200 Date: Tue, 21 Aug 2012 13:09:45 GMT Content-Length: 877 Server: Apache-Coyote/1.1 Content-Type: application/json version: 12312.111 

И последнее, хотелось иметь «красивый», лучше скажем нужный нам урл к вебсервисам. Кнечно, можно поправить server.xml или использовать какой-нибудь тул для urlRewrite, но по моему самый простой способ это запаковать наш веб архив в ear и задать другой рут для наших веб-сервисов в application.xml, но в рамках данного поста я этого уже делать не буду.

P.S.: Надеюсь, что данный пост будет полезен тем кто хочет познакомиться с Java RESTful web services, а более опытные посоветуют и покритикуют!

Источник

Оцените статью