/* * Copyright (c) 2016 Network New Technologies Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.networknt.client; import com.networknt.config.Config; import com.networknt.security.JwtHelper; import com.networknt.utility.Constants; import com.networknt.exception.ExpiredTokenException; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.util.EntityUtils; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.NumericDate; import org.jose4j.jwt.consumer.InvalidJwtException; import org.junit.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.*; public class ClientTest { static final Logger logger = LoggerFactory.getLogger(ClientTest.class); static Undertow server = null; @Before public void setUp() { if(server == null) { logger.info("starting server"); server = Undertow.builder() .addHttpListener(8887, "localhost") .setHandler(Handlers.header(Handlers.path() .addPrefixPath("/api", new ApiHandler()) .addPrefixPath("/oauth2/token", new OAuthHandler()), Headers.SERVER_STRING, "U-tow")) .build(); server.start(); } } @AfterClass public static void tearDown() throws Exception { if(server != null) { try { Thread.sleep(100); } catch (InterruptedException ignored) { } server.stop(); System.out.println("The server is stopped."); try { Thread.sleep(100); } catch (InterruptedException ignored) { } } } final class ApiHandler implements HttpHandler { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { boolean hasScopeToken = exchange.getRequestHeaders().contains(Constants.SCOPE_TOKEN); Assert.assertTrue(hasScopeToken); String scopeToken = exchange.getRequestHeaders().get(Constants.SCOPE_TOKEN, 0); boolean expired = isTokenExpired(scopeToken); Assert.assertFalse(expired); exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json"); exchange.getResponseSender().send(ByteBuffer.wrap( Config.getInstance().getMapper().writeValueAsBytes( Collections.singletonMap("message", "OK!")))); } } final class OAuthHandler implements HttpHandler { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { try { int sleepTime = randInt(1, 3) * 1000; if(sleepTime >= 2000) { sleepTime = 3000; } else { sleepTime = 1000; } Thread.sleep(sleepTime); } catch (Exception e) { e.printStackTrace(); } // create a token that expired in 5 seconds. Map<String, Object> map = new HashMap<>(); String token = getJwt(5); map.put("access_token", token); map.put("token_type", "Bearer"); map.put("expires_in", 5); exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json"); exchange.getResponseSender().send(ByteBuffer.wrap( Config.getInstance().getMapper().writeValueAsBytes(map))); } } @Test public void testSingleSychClient() throws Exception { callApiSync(); } @Test public void testSingleAsychClient() throws Exception { callApiAsync(); } public String callApiSync() throws Exception { String url = "http://localhost:8887/api"; CloseableHttpClient client = Client.getInstance().getSyncClient(); HttpGet httpGet = new HttpGet(url); ResponseHandler<String> responseHandler = response -> { int status = response.getStatusLine().getStatusCode(); Assert.assertEquals(200, status); if (status >= 200 && status < 300) { HttpEntity entity = response.getEntity(); return entity != null ? EntityUtils.toString(entity) : null; } else { throw new ClientProtocolException("Unexpected response status: " + status); } }; String responseBody; try { Client.getInstance().populateHeader(httpGet, "Bearer token", "cid", "tid"); responseBody = client.execute(httpGet, responseHandler); Assert.assertEquals("{\"message\":\"OK!\"}", responseBody); logger.debug("message = " + responseBody); } catch (Exception e) { e.printStackTrace(); responseBody = "{\"message\":\"Error!\"}"; } return responseBody; } public String callApiAsync() throws Exception { String url = "http://localhost:8887/api"; CloseableHttpAsyncClient client = Client.getInstance().getAsyncClient(); HttpGet httpGet = new HttpGet(url); try { Client.getInstance().populateHeader(httpGet, "Bearer token", "cid", "tid"); Future<HttpResponse> future = client.execute(httpGet, null); HttpResponse response = future.get(); int status = response.getStatusLine().getStatusCode(); Assert.assertEquals(200, status); HttpEntity entity = response.getEntity(); String result = EntityUtils.toString(entity); Assert.assertEquals("{\"message\":\"OK!\"}", result); logger.debug("message = " + result); return result; } catch (Exception e) { e.printStackTrace(); return "{\"message\":\"Error!\"}"; } } private void callApiSyncMultiThread(final int threadCount) throws InterruptedException, ExecutionException { Callable<String> task = this::callApiSync; List<Callable<String>> tasks = Collections.nCopies(threadCount, task); long start = System.currentTimeMillis(); ExecutorService executorService = Executors.newFixedThreadPool(threadCount); List<Future<String>> futures = executorService.invokeAll(tasks); List<String> resultList = new ArrayList<>(futures.size()); // Check for exceptions for (Future<String> future : futures) { // Throws an exception if an exception was thrown by the task. resultList.add(future.get()); } long last = (System.currentTimeMillis() - start); System.out.println("resultList = " + resultList + " response time = " + last); } //@Test public void testSyncAboutToExpire() throws InterruptedException, ExecutionException { for(int i = 0; i < 100; i++) { callApiSyncMultiThread(4); logger.info("called times: " + i); try { Thread.sleep(1000); } catch (InterruptedException ignored) { } } } //@Test public void testSyncExpired() throws InterruptedException, ExecutionException { for(int i = 0; i < 100; i++) { callApiSyncMultiThread(4); logger.info("called times: " + i); try { Thread.sleep(6000); } catch (InterruptedException ignored) { } } } //@Test public void testMixed() throws InterruptedException, ExecutionException { for(int i = 0; i < 100; i++) { callApiSyncMultiThread(4 ); logger.info("called times: " + i); try { int sleepTime = randInt(1, 6) * 1000; if (sleepTime > 3000) { sleepTime = 6000; } else { sleepTime = 1000; } Thread.sleep(sleepTime); } catch (InterruptedException ignored) { } } } private void callApiAsyncMultiThread(final int threadCount) throws InterruptedException, ExecutionException { Callable<String> task = this::callApiAsync; List<Callable<String>> tasks = Collections.nCopies(threadCount, task); ExecutorService executorService = Executors.newFixedThreadPool(threadCount); List<Future<String>> futures = executorService.invokeAll(tasks); List<String> resultList = new ArrayList<>(futures.size()); for (Future<String> future : futures) { resultList.add(future.get()); } System.out.println("resultList = " + resultList); } //@Test public void testAsyncAboutToExpire() throws InterruptedException, ExecutionException { for(int i = 0; i < 10; i++) { callApiAsyncMultiThread(4); logger.info("called times: " + i); try { Thread.sleep(1000); } catch (InterruptedException ignored) { } } } //@Test public void testAsyncExpired() throws InterruptedException, ExecutionException { for(int i = 0; i < 10; i++) { callApiAsyncMultiThread(4); logger.info("called times: " + i); try { Thread.sleep(6000); } catch (InterruptedException ignored) { } } } private static int randInt(int min, int max) { Random rand = new Random(); return rand.nextInt((max-min) + 1) + min; } private boolean isTokenExpired(String authorization) { boolean expired = false; String jwt = JwtHelper.getJwtFromAuthorization(authorization); if(jwt != null) { try { JwtHelper.verifyJwt(jwt); } catch(InvalidJwtException e) { e.printStackTrace(); } catch(ExpiredTokenException e) { expired = true; } } return expired; } private String getJwt(int expiredInSeconds) throws Exception { JwtClaims claims = getTestClaims(); claims.setExpirationTime(NumericDate.fromMilliseconds(System.currentTimeMillis() + expiredInSeconds * 1000)); return JwtHelper.getJwt(claims); } private JwtClaims getTestClaims() { JwtClaims claims = JwtHelper.getDefaultJwtClaims(); claims.setClaim("user_id", "steve"); claims.setClaim("user_type", "EMPLOYEE"); claims.setClaim("client_id", "aaaaaaaa-1234-1234-1234-bbbbbbbb"); List<String> scope = Arrays.asList("api.r", "api.w"); claims.setStringListClaim("scope", scope); // multi-valued claims work too and will end up as a JSON array return claims; } }