001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase; 019 020import java.io.IOException; 021import java.io.PrintWriter; 022import java.net.URI; 023import java.util.HashMap; 024import java.util.Map; 025import java.util.concurrent.locks.ReadWriteLock; 026import java.util.concurrent.locks.ReentrantReadWriteLock; 027import java.util.function.BiConsumer; 028import java.util.regex.Pattern; 029import javax.servlet.http.HttpServletRequest; 030import javax.servlet.http.HttpServletResponse; 031import javax.ws.rs.core.MediaType; 032import org.junit.rules.ExternalResource; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Request; 037import org.apache.hbase.thirdparty.org.eclipse.jetty.server.RequestLog; 038import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Server; 039import org.apache.hbase.thirdparty.org.eclipse.jetty.server.ServerConnector; 040import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Slf4jRequestLog; 041import org.apache.hbase.thirdparty.org.eclipse.jetty.server.handler.AbstractHandler; 042import org.apache.hbase.thirdparty.org.eclipse.jetty.util.RegexSet; 043 044/** 045 * A {@link org.junit.Rule} that manages a simple http server. The caller registers request 046 * handlers to URI path regexp. 047 */ 048public class MockHttpApiRule extends ExternalResource { 049 private static final Logger LOG = LoggerFactory.getLogger(MockHttpApiRule.class); 050 051 private MockHandler handler; 052 private Server server; 053 054 /** 055 * Register a callback handler for the specified path target. 056 */ 057 public MockHttpApiRule addRegistration( 058 final String pathRegex, 059 final BiConsumer<String, HttpServletResponse> responder 060 ) { 061 handler.register(pathRegex, responder); 062 return this; 063 } 064 065 /** 066 * Shortcut method for calling {@link #addRegistration(String, BiConsumer)} with a 200 response. 067 */ 068 public MockHttpApiRule registerOk(final String pathRegex, final String responseBody) { 069 return addRegistration(pathRegex, (target, resp) -> { 070 try { 071 resp.setStatus(HttpServletResponse.SC_OK); 072 resp.setCharacterEncoding("UTF-8"); 073 resp.setContentType(MediaType.APPLICATION_JSON_TYPE.toString()); 074 final PrintWriter writer = resp.getWriter(); 075 writer.write(responseBody); 076 writer.flush(); 077 } catch (IOException e) { 078 throw new RuntimeException(e); 079 } 080 }); 081 } 082 083 public void clearRegistrations() { 084 handler.clearRegistrations(); 085 } 086 087 /** 088 * Retrieve the service URI for this service. 089 */ 090 public URI getURI() { 091 if (server == null || !server.isRunning()) { 092 throw new IllegalStateException("server is not running"); 093 } 094 return server.getURI(); 095 } 096 097 @Override 098 protected void before() throws Exception { 099 handler = new MockHandler(); 100 server = new Server(); 101 final ServerConnector http = new ServerConnector(server); 102 http.setHost("localhost"); 103 server.addConnector(http); 104 server.setStopAtShutdown(true); 105 server.setHandler(handler); 106 server.setRequestLog(buildRequestLog()); 107 server.start(); 108 } 109 110 @Override 111 protected void after() { 112 try { 113 server.stop(); 114 } catch (Exception e) { 115 throw new RuntimeException(e); 116 } 117 } 118 119 private static RequestLog buildRequestLog() { 120 final Slf4jRequestLog requestLog = new Slf4jRequestLog(); 121 requestLog.setLoggerName(LOG.getName() + ".RequestLog"); 122 requestLog.setExtended(true); 123 return requestLog; 124 } 125 126 private static class MockHandler extends AbstractHandler { 127 128 private final ReadWriteLock responseMappingLock = new ReentrantReadWriteLock(); 129 private final Map<String, BiConsumer<String, HttpServletResponse>> responseMapping = 130 new HashMap<>(); 131 private final RegexSet regexSet = new RegexSet(); 132 133 void register( 134 final String pathRegex, 135 final BiConsumer<String, HttpServletResponse> responder 136 ) { 137 LOG.debug("Registering responder to '{}'", pathRegex); 138 responseMappingLock.writeLock().lock(); 139 try { 140 responseMapping.put(pathRegex, responder); 141 regexSet.add(pathRegex); 142 } finally { 143 responseMappingLock.writeLock().unlock(); 144 } 145 } 146 147 void clearRegistrations() { 148 LOG.debug("Clearing registrations"); 149 responseMappingLock.writeLock().lock(); 150 try { 151 responseMapping.clear(); 152 regexSet.clear(); 153 } finally { 154 responseMappingLock.writeLock().unlock(); 155 } 156 } 157 158 @Override 159 public void handle( 160 final String target, 161 final Request baseRequest, 162 final HttpServletRequest request, 163 final HttpServletResponse response 164 ) { 165 responseMappingLock.readLock().lock(); 166 try { 167 if (!regexSet.matches(target)) { 168 response.setStatus(HttpServletResponse.SC_NOT_FOUND); 169 return; 170 } 171 responseMapping.entrySet() 172 .stream() 173 .filter(e -> Pattern.matches(e.getKey(), target)) 174 .findAny() 175 .map(Map.Entry::getValue) 176 .orElseThrow(() -> noMatchFound(target)) 177 .accept(target, response); 178 } finally { 179 responseMappingLock.readLock().unlock(); 180 } 181 } 182 183 private static RuntimeException noMatchFound(final String target) { 184 return new RuntimeException( 185 String.format("Target path '%s' matches no registered regex.", target)); 186 } 187 } 188}