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 org.junit.rules.ExternalResource; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType; 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 handlers 046 * 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(final String pathRegex, 058 final BiConsumer<String, HttpServletResponse> responder) { 059 handler.register(pathRegex, responder); 060 return this; 061 } 062 063 /** 064 * Shortcut method for calling {@link #addRegistration(String, BiConsumer)} with a 200 response. 065 */ 066 public MockHttpApiRule registerOk(final String pathRegex, final String responseBody) { 067 return addRegistration(pathRegex, (target, resp) -> { 068 try { 069 resp.setStatus(HttpServletResponse.SC_OK); 070 resp.setCharacterEncoding("UTF-8"); 071 resp.setContentType(MediaType.APPLICATION_JSON_TYPE.toString()); 072 final PrintWriter writer = resp.getWriter(); 073 writer.write(responseBody); 074 writer.flush(); 075 } catch (IOException e) { 076 throw new RuntimeException(e); 077 } 078 }); 079 } 080 081 public void clearRegistrations() { 082 handler.clearRegistrations(); 083 } 084 085 /** 086 * Retrieve the service URI for this service. 087 */ 088 public URI getURI() { 089 if (server == null || !server.isRunning()) { 090 throw new IllegalStateException("server is not running"); 091 } 092 return server.getURI(); 093 } 094 095 @Override 096 protected void before() throws Exception { 097 handler = new MockHandler(); 098 server = new Server(); 099 final ServerConnector http = new ServerConnector(server); 100 http.setHost("localhost"); 101 server.addConnector(http); 102 server.setStopAtShutdown(true); 103 server.setHandler(handler); 104 server.setRequestLog(buildRequestLog()); 105 server.start(); 106 } 107 108 @Override 109 protected void after() { 110 try { 111 server.stop(); 112 } catch (Exception e) { 113 throw new RuntimeException(e); 114 } 115 } 116 117 private static RequestLog buildRequestLog() { 118 final Slf4jRequestLog requestLog = new Slf4jRequestLog(); 119 requestLog.setLoggerName(LOG.getName() + ".RequestLog"); 120 requestLog.setExtended(true); 121 return requestLog; 122 } 123 124 private static class MockHandler extends AbstractHandler { 125 126 private final ReadWriteLock responseMappingLock = new ReentrantReadWriteLock(); 127 private final Map<String, BiConsumer<String, HttpServletResponse>> responseMapping = 128 new HashMap<>(); 129 private final RegexSet regexSet = new RegexSet(); 130 131 void register(final String pathRegex, final BiConsumer<String, HttpServletResponse> responder) { 132 LOG.debug("Registering responder to '{}'", pathRegex); 133 responseMappingLock.writeLock().lock(); 134 try { 135 responseMapping.put(pathRegex, responder); 136 regexSet.add(pathRegex); 137 } finally { 138 responseMappingLock.writeLock().unlock(); 139 } 140 } 141 142 void clearRegistrations() { 143 LOG.debug("Clearing registrations"); 144 responseMappingLock.writeLock().lock(); 145 try { 146 responseMapping.clear(); 147 regexSet.clear(); 148 } finally { 149 responseMappingLock.writeLock().unlock(); 150 } 151 } 152 153 @Override 154 public void handle(final String target, final Request baseRequest, 155 final HttpServletRequest request, final HttpServletResponse response) { 156 responseMappingLock.readLock().lock(); 157 try { 158 if (!regexSet.matches(target)) { 159 response.setStatus(HttpServletResponse.SC_NOT_FOUND); 160 return; 161 } 162 responseMapping.entrySet().stream().filter(e -> Pattern.matches(e.getKey(), target)) 163 .findAny().map(Map.Entry::getValue).orElseThrow(() -> noMatchFound(target)) 164 .accept(target, response); 165 } finally { 166 responseMappingLock.readLock().unlock(); 167 } 168 } 169 170 private static RuntimeException noMatchFound(final String target) { 171 return new RuntimeException( 172 String.format("Target path '%s' matches no registered regex.", target)); 173 } 174 } 175}