1+ /*
2+ * ====================================================================
3+ * Licensed to the Apache Software Foundation (ASF) under one
4+ * or more contributor license agreements. See the NOTICE file
5+ * distributed with this work for additional information
6+ * regarding copyright ownership. The ASF licenses this file
7+ * to you under the Apache License, Version 2.0 (the
8+ * "License"); you may not use this file except in compliance
9+ * with the License. You may obtain a copy of the License at
10+ *
11+ * http://www.apache.org/licenses/LICENSE-2.0
12+ *
13+ * Unless required by applicable law or agreed to in writing,
14+ * software distributed under the License is distributed on an
15+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+ * KIND, either express or implied. See the License for the
17+ * specific language governing permissions and limitations
18+ * under the License.
19+ * ====================================================================
20+ *
21+ * This software consists of voluntary contributions made by many
22+ * individuals on behalf of the Apache Software Foundation. For more
23+ * information on the Apache Software Foundation, please see
24+ * <http://www.apache.org/>.
25+ *
26+ */
27+ package org .apache .hc .client5 .http .rest .examples ;
28+
29+ import java .io .IOException ;
30+ import java .io .InputStream ;
31+ import java .io .OutputStream ;
32+ import java .net .InetSocketAddress ;
33+ import java .net .URI ;
34+ import java .nio .charset .StandardCharsets ;
35+
36+ import com .fasterxml .jackson .databind .ObjectMapper ;
37+ import com .sun .net .httpserver .HttpExchange ;
38+ import com .sun .net .httpserver .HttpServer ;
39+
40+ import jakarta .ws .rs .Consumes ;
41+ import jakarta .ws .rs .GET ;
42+ import jakarta .ws .rs .HeaderParam ;
43+ import jakarta .ws .rs .POST ;
44+ import jakarta .ws .rs .Path ;
45+ import jakarta .ws .rs .PathParam ;
46+ import jakarta .ws .rs .Produces ;
47+ import jakarta .ws .rs .QueryParam ;
48+ import org .apache .hc .client5 .http .impl .async .CloseableHttpAsyncClient ;
49+ import org .apache .hc .client5 .http .impl .async .HttpAsyncClients ;
50+ import org .apache .hc .client5 .http .rest .RestClientBuilder ;
51+
52+ public final class RestClientMain {
53+
54+ private RestClientMain () {
55+ }
56+
57+ public static void main (final String [] args ) throws Exception {
58+ final ObjectMapper objectMapper = new ObjectMapper ();
59+ final HttpServer server = HttpServer .create (new InetSocketAddress (0 ), 0 );
60+ server .createContext ("/books" , exchange -> handleBooks (exchange , objectMapper ));
61+ server .start ();
62+
63+ final URI baseUri = new URI ("http://localhost:" + server .getAddress ().getPort ());
64+
65+ try (final CloseableHttpAsyncClient httpClient = HttpAsyncClients .createDefault ()) {
66+ httpClient .start ();
67+
68+ final BookResource client = RestClientBuilder .newBuilder ()
69+ .baseUri (baseUri )
70+ .httpClient (httpClient )
71+ .objectMapper (objectMapper )
72+ .build (BookResource .class );
73+
74+ final Book created = client .createBook (
75+ "abc-123" ,
76+ "en" ,
77+ "token-1" ,
78+ new Book (null , "HttpComponents in Action" )
79+ );
80+
81+ System .out .println ("Created:" );
82+ System .out .println (" id = " + created .id );
83+ System .out .println (" title = " + created .title );
84+ System .out .println (" lang = " + created .lang );
85+
86+ final Book fetched = client .getBook ("abc-123" , "en" , "token-1" );
87+
88+ System .out .println ("Fetched:" );
89+ System .out .println (" id = " + fetched .id );
90+ System .out .println (" title = " + fetched .title );
91+ System .out .println (" lang = " + fetched .lang );
92+ } finally {
93+ server .stop (0 );
94+ }
95+ }
96+
97+ private static void handleBooks (final HttpExchange exchange ,
98+ final ObjectMapper objectMapper ) throws IOException {
99+ try {
100+ final String method = exchange .getRequestMethod ();
101+ final String path = exchange .getRequestURI ().getPath ();
102+ final String query = exchange .getRequestURI ().getRawQuery ();
103+ final String auth = exchange .getRequestHeaders ().getFirst ("X-Auth" );
104+
105+ if (!"token-1" .equals (auth )) {
106+ send (exchange , 401 , "text/plain" , "Unauthorized" );
107+ return ;
108+ }
109+
110+ if ("GET" .equals (method ) && path .startsWith ("/books/" )) {
111+ final String id = path .substring ("/books/" .length ());
112+ final String lang = extractQueryParam (query , "lang" );
113+
114+ final Book book = new Book (id , "HttpComponents in Action" );
115+ book .lang = lang ;
116+
117+ sendJson (exchange , 200 , objectMapper , book );
118+ return ;
119+ }
120+
121+ if ("POST" .equals (method ) && "/books" .equals (path )) {
122+ final String id = extractQueryParam (query , "id" );
123+ final String lang = extractQueryParam (query , "lang" );
124+
125+ final Book incoming = readJson (exchange .getRequestBody (), objectMapper , Book .class );
126+ incoming .id = id ;
127+ incoming .lang = lang ;
128+
129+ sendJson (exchange , 200 , objectMapper , incoming );
130+ return ;
131+ }
132+
133+ send (exchange , 404 , "text/plain" , "Not found" );
134+ } finally {
135+ exchange .close ();
136+ }
137+ }
138+
139+ private static <T > T readJson (final InputStream inputStream ,
140+ final ObjectMapper objectMapper ,
141+ final Class <T > type ) throws IOException {
142+ return objectMapper .readValue (inputStream , type );
143+ }
144+
145+ private static void sendJson (final HttpExchange exchange ,
146+ final int statusCode ,
147+ final ObjectMapper objectMapper ,
148+ final Object payload ) throws IOException {
149+ final byte [] body = objectMapper .writeValueAsBytes (payload );
150+ exchange .getResponseHeaders ().add ("Content-Type" , "application/json" );
151+ exchange .sendResponseHeaders (statusCode , body .length );
152+ try (final OutputStream out = exchange .getResponseBody ()) {
153+ out .write (body );
154+ }
155+ }
156+
157+ private static void send (final HttpExchange exchange ,
158+ final int statusCode ,
159+ final String contentType ,
160+ final String body ) throws IOException {
161+ final byte [] bytes = body .getBytes (StandardCharsets .UTF_8 );
162+ exchange .getResponseHeaders ().add ("Content-Type" , contentType + "; charset=UTF-8" );
163+ exchange .sendResponseHeaders (statusCode , bytes .length );
164+ try (final OutputStream out = exchange .getResponseBody ()) {
165+ out .write (bytes );
166+ }
167+ }
168+
169+ private static String extractQueryParam (final String query , final String name ) {
170+ if (query == null || query .isEmpty ()) {
171+ return null ;
172+ }
173+ final String prefix = name + "=" ;
174+ for (final String pair : query .split ("&" )) {
175+ if (pair .startsWith (prefix )) {
176+ return pair .substring (prefix .length ());
177+ }
178+ }
179+ return null ;
180+ }
181+
182+ @ Path ("/books" )
183+ interface BookResource {
184+
185+ @ GET
186+ @ Path ("/{id}" )
187+ @ Produces ("application/json" )
188+ Book getBook (@ PathParam ("id" ) String id ,
189+ @ QueryParam ("lang" ) String lang ,
190+ @ HeaderParam ("X-Auth" ) String auth );
191+
192+ @ POST
193+ @ Consumes ("application/json" )
194+ @ Produces ("application/json" )
195+ Book createBook (@ QueryParam ("id" ) String id ,
196+ @ QueryParam ("lang" ) String lang ,
197+ @ HeaderParam ("X-Auth" ) String auth ,
198+ Book book );
199+ }
200+
201+ static final class Book {
202+
203+ public String id ;
204+ public String title ;
205+ public String lang ;
206+
207+ Book () {
208+ }
209+
210+ Book (final String id , final String title ) {
211+ this .id = id ;
212+ this .title = title ;
213+ }
214+ }
215+ }
0 commit comments