📜 ⬆️ ⬇️

We implement RESTful Web Service on java

Good day to all farmers!

The reason for writing the article was that, to my great surprise at Habré, I did not find an article about the implementation of the RESTful Web Service in Java, but, of course, I didn’t look well. Yes, there is a lot written about RESTful web services, but somehow it’s just that, with simple code examples, a working service, it’s not so easy to find it and not only in Habré ...

In general, I got acquainted with REST only recently, not more than a month ago. So I will be very grateful for the advice, amendments and criticism!
')
It was not so difficult to understand, and so, but I think a similar post would help me a lot and would greatly speed up the learning process! Especially if you are a novice developer and have only heard a lot about things, but have never touched them with your hands.

In my first impression: the thing is really very convenient, and most importantly, very simple, and if you use JSON , and not XML , well, at least it seemed to me after the experience with SOAP and WSDL . Well, yes, I think about it, and so everyone knows who has worked at least a little with web services.

So, who is interested in the implementation, please under the cat

Immediately make a reservation:

1. All the code, of course, cannot be laid out in the article and cannot be told about it;
2. the project version is, of course, not final, and, as I said above, I will be very grateful for comments and advice;
3. Of course, there are bugs.

And so, let's start in order:

1. Used software:


The software was chosen on a very simple principle - the simpler, the better. Yes, instead of MySQL for loaded services without having to make complex queries to the database, MongoDB is very good to use, well, at least there is a lot written about this, and again it is more convenient to use it since the same JSON is used at the input.

2. In principle, what our service will do is that everything is very trite here: the service will work with a single table in the database - insert, update, delete, and, of course, receive records in a list or by Id. Of course, I would like to be able to parameterize a request for a list of records, it would not be bad to make a “beautiful” URL to the service, to tie some interceptor, for example, to check the user's rights to access the service, or to do something else. before starting the service, well, somehow centrally manage the error codes in the responses from the server.

Actually, the sign:

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 ) 


WS Endpoints:

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

  2. http://mysite.com/service/customer/{id} 


4 standard statuses that we will additionally process (for example, to add a version of our web services in response and in case of error our error code):

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

3. Implementation (github code here ):

Yes, the code, minimally commented, the description of annotations here .

Web services themselves:
 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("/{id}") 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("/{id}") 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)); List<Customer> listCust = customersDAO.getCustomersList(parameters); if (listCust != null) { GenericEntity<List<Customer>> entity = new GenericEntity<List<Customer>>( listCust) { }; return ResponseCreator.success(getHeaderVersion(), entity); } else { return ResponseCreator.error(404, Error.NOT_FOUND.getCode(), getHeaderVersion()); } } } 


Everything is quite simple - 4 web services depending on the URI and the method by which this URI is pulled, there is a DAO object that is connected in beans.xml and access to the request headers to get for example the custom “version” header.

A thing that works before the service is called:
 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<String, List<String>> 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)); } } } 


Here, in the validate () method, you can check some preconditions, purely for the test, the verification of the custom header in the request is added to the session identifier "ss_id", well, and from the first time even with this header will fall 401.

General exceptions handler:
 public class CustomExceptionMapper implements ExceptionMapper<Exception> { @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()); } } 


Something is already a bit too much code for the post, there is another helper class for generating a response to the server and a global enum for storing our error codes. Yes, the deployment descriptor and beans.xml still give here:

web.xml:
 ... <web-app> <display-name>service</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/beans.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>CXFServlet</servlet-name> <display-name>CXF Servlet</display-name> <servlet-class> org.apache.cxf.transport.servlet.CXFServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app> 


Here the main interest is the connection of the servlet from Apache - CXFServlet and the standard SprL ContextLoaderListener.

beans.xml:
 ... <!-- Imported resources for cxf --> <import resource="classpath:META-INF/cxf/cxf.xml" /> <import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" /> <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> <!-- Imported bean for dao --> <import resource="classpath:META-INF/spring/dao.xml"/> <bean id="customersService" class="com.test.services.customers.rest.CustomersServiceJSON"> <property name="customersDAO" ref="customersDAO"/> </bean> <bean id="preInvokeHandler" class="com.test.services.rest.PreInvokeHandler" /> <bean id="customExceptionMapper" class="com.test.services.rest.CustomExceptionMapper" /> <jaxrs:server id="restContainer" address="/customer"> <jaxrs:serviceBeans> <ref bean="customersService" /> </jaxrs:serviceBeans> <jaxrs:providers> <ref bean="preInvokeHandler" /> <ref bean="customExceptionMapper" /> </jaxrs:providers> </jaxrs:server> ......... 


Here, in fact, we set the necessary configuration files for CXF, connected the DAO object, our preprocessor and exception handler, of course, the bin itself with services and set the root for the services.

In order to pull services, I used the REST Console 4.0.2 plugin for chrome - the thing is quite simple, the main thing is to set the desired endpoint, custom headers (as I said without “ss_id” will always fall 401) and the content type. For example:

Request Body:
 Request Url: http://localhost:8080/service/customer Request Method: GET Status Code: 200 

Request headers:
 Accept: application/json Content-Type: application/json ss_id: 12312.111 version: 12312.111 ........ 

Response headers:
 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 

Response body:
 { "customer": [{ "id": "89ad5a46-c9a2-493f-a583-d8250ee31766", "adress": "null", "contract_id": "null", "first_name": "serg", "last_name": "serg", "mail": "serg", "phone": "null" }, { "id": "300ff688-a783-4e6a-9048-8bb625128dc0", "first_name": "serg" }, { "id": "67731ab9-87b1-4ff9-a7e4-618c1f9e8c4c", "first_name": "serg" }, { "id": "cd5039bb-031f-4697-a70c-ad3e628963dd", "first_name": "serg" }, { "id": "86da5446-7439-4242-b730-31c8b57a5c7d", "first_name": "serg" }, .......... 


And finally, I wanted to have a “beautiful”, better say, url to web services we need. Of course, you can fix server.xml or use some tool for urlRewrite, but in my opinion the easiest way is to pack our web archive in ear and set another root for our web services in application.xml, but in this post I’m I will not do it already.

PS: I hope that this post will be useful to those who want to get acquainted with Java RESTful web services, and the more experienced will advise and criticize!

Source: https://habr.com/ru/post/150034/


All Articles