Java tutorial
/* * Copyright 2014 JBoss 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 io.apiman.gateway.platforms.servlet; import io.apiman.gateway.engine.IEngine; import io.apiman.gateway.engine.IEngineResult; import io.apiman.gateway.engine.IServiceRequestExecutor; import io.apiman.gateway.engine.async.IAsyncHandler; import io.apiman.gateway.engine.async.IAsyncResult; import io.apiman.gateway.engine.async.IAsyncResultHandler; import io.apiman.gateway.engine.beans.PolicyFailure; import io.apiman.gateway.engine.beans.PolicyFailureType; import io.apiman.gateway.engine.beans.ServiceRequest; import io.apiman.gateway.engine.beans.ServiceResponse; import io.apiman.gateway.engine.io.IApimanBuffer; import io.apiman.gateway.engine.io.ISignalWriteStream; import io.apiman.gateway.platforms.servlet.i18n.Messages; import io.apiman.gateway.platforms.servlet.io.ByteBuffer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.Enumeration; import java.util.Map; import java.util.Map.Entry; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.codehaus.jackson.map.ObjectMapper; /** * The API Management gateway servlet. This servlet is responsible for converting inbound * http servlet requests into {@link ServiceRequest}s so that they can be fed into the * API Management machinery. It also is responsible for converting the resulting * {@link ServiceResponse} into an HTTP Servlet Response that is suitable for returning * to the caller. * * @author eric.wittmann@redhat.com */ public abstract class GatewayServlet extends HttpServlet { private static final long serialVersionUID = 958726685958622333L; private static final ObjectMapper mapper = new ObjectMapper(); /** * Constructor. */ public GatewayServlet() { } /** * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doAction(req, resp, "GET"); //$NON-NLS-1$ } /** * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doAction(req, resp, "POST"); //$NON-NLS-1$ } /** * @see javax.servlet.http.HttpServlet#doPut(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doAction(req, resp, "PUT"); //$NON-NLS-1$ } /** * @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doAction(req, resp, "DELETE"); //$NON-NLS-1$ } /** * Generic handler for all types of http actions/verbs. * @param req * @param resp * @param action */ protected void doAction(final HttpServletRequest req, final HttpServletResponse resp, String action) { try { ServiceRequest srequest = readRequest(req); srequest.setType(action); IServiceRequestExecutor executor = getEngine().executor(srequest, new IAsyncResultHandler<IEngineResult>() { @Override public void handle(IAsyncResult<IEngineResult> asyncResult) { if (asyncResult.isSuccess()) { IEngineResult engineResult = asyncResult.getResult(); if (engineResult.isResponse()) { try { writeResponse(resp, engineResult.getServiceResponse()); final ServletOutputStream outputStream = resp.getOutputStream(); engineResult.bodyHandler(new IAsyncHandler<IApimanBuffer>() { public void handle(IApimanBuffer chunk) { try { if (chunk instanceof ByteBuffer) { byte[] buffer = (byte[]) chunk.getNativeBuffer(); outputStream.write(buffer, 0, chunk.length()); } else { outputStream.write(chunk.getBytes()); } } catch (IOException e) { // This will get caught by the service connector, which will abort the // connection to the back-end service. throw new RuntimeException(e); } } }); engineResult.endHandler(new IAsyncHandler<Void>() { @Override public void handle(Void result) { try { resp.flushBuffer(); } catch (IOException e) { // This will get caught by the service connector, which will abort the // connection to the back-end service. throw new RuntimeException(e); } } }); } catch (IOException e) { // this would mean we couldn't get the output stream from the response, so we // need to abort the engine result (which will let the back-end connection // close down). engineResult.abort(); throw new RuntimeException(e); } } else { writeFailure(resp, engineResult.getPolicyFailure()); } } else { writeError(resp, asyncResult.getError()); } } }); executor.streamHandler(new IAsyncHandler<ISignalWriteStream>() { @Override public void handle(ISignalWriteStream connectorStream) { try { final InputStream is = req.getInputStream(); ByteBuffer buffer = new ByteBuffer(2048); int numBytes = buffer.readFrom(is); while (numBytes != -1) { connectorStream.write(buffer); numBytes = buffer.readFrom(is); } connectorStream.end(); } catch (IOException e) { connectorStream.abort(); throw new RuntimeException(e); } } }); executor.execute(); } catch (Throwable e) { writeError(resp, e); } finally { GatewayThreadContext.reset(); } } /** * Gets the engine - subclasses must implement this. * @return gets the engine */ protected abstract IEngine getEngine(); /** * Reads a {@link ServiceRequest} from information found in the inbound * portion of the http request. * @param request the undertow http server request * @return a valid {@link ServiceRequest} * @throws IOException */ protected ServiceRequest readRequest(HttpServletRequest request) throws Exception { String apiKey = getApiKey(request); if (apiKey == null) { throw new Exception(Messages.i18n.format("GatewayServlet.MissingAPIKey")); //$NON-NLS-1$ } ServiceRequest srequest = GatewayThreadContext.getServiceRequest(); srequest.setApiKey(apiKey); srequest.setDestination(getDestination(request)); readHeaders(srequest, request); srequest.setRawRequest(request); srequest.setRemoteAddr(request.getRemoteAddr()); return srequest; } /** * Gets the API Key from the request. The API key can be passed either via * a custom http request header called X-API-Key or else by a query parameter * in the URL called apikey. * @param request the inbound request * @return the api key or null if not found */ protected String getApiKey(HttpServletRequest request) { String apiKey = request.getHeader("X-API-Key"); //$NON-NLS-1$ if (apiKey == null || apiKey.trim().length() == 0) { apiKey = getApiKeyFromQuery(request); } return apiKey; } /** * Gets the API key from the request's query string. * @param request the inbound request * @return the api key or null if not found */ protected String getApiKeyFromQuery(HttpServletRequest request) { String queryString = request.getQueryString(); if (queryString == null) { return null; } int idx = queryString.indexOf("apikey="); //$NON-NLS-1$ if (idx >= 0) { int endIdx = queryString.indexOf('&', idx); if (endIdx == -1) { endIdx = queryString.length(); } return queryString.substring(idx + 7, endIdx); } else { return null; } } /** * Returns the path to the resource. * @param request */ protected String getDestination(HttpServletRequest request) { String path = request.getPathInfo(); return path; } /** * Reads the inbound request headers from the request and sets them on * the {@link ServiceRequest}. * @param request * @param request */ protected void readHeaders(ServiceRequest srequest, HttpServletRequest request) { Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String hname = headerNames.nextElement(); String hval = request.getHeader(hname); srequest.getHeaders().put(hname, hval); } } /** * Writes the service response to the HTTP servlet response object. * @param response * @param sresponse */ protected void writeResponse(HttpServletResponse response, ServiceResponse sresponse) { response.setStatus(sresponse.getCode()); Map<String, String> headers = sresponse.getHeaders(); for (Map.Entry<String, String> entry : headers.entrySet()) { String hname = entry.getKey(); String hval = entry.getValue(); response.setHeader(hname, hval); } } /** * Writes a policy failure to the http response. * @param resp * @param policyFailure */ private void writeFailure(HttpServletResponse resp, PolicyFailure policyFailure) { resp.setContentType("application/json"); //$NON-NLS-1$ resp.setHeader("X-Policy-Failure-Type", String.valueOf(policyFailure.getType())); //$NON-NLS-1$ resp.setHeader("X-Policy-Failure-Message", policyFailure.getMessage()); //$NON-NLS-1$ resp.setHeader("X-Policy-Failure-Code", String.valueOf(policyFailure.getFailureCode())); //$NON-NLS-1$ for (Entry<String, String> entry : policyFailure.getHeaders().entrySet()) { resp.setHeader(entry.getKey(), entry.getValue()); } int errorCode = 500; if (policyFailure.getType() == PolicyFailureType.Authentication) { errorCode = 401; } else if (policyFailure.getType() == PolicyFailureType.Authorization) { errorCode = 403; } resp.setStatus(errorCode); try { mapper.writer().writeValue(resp.getOutputStream(), policyFailure); IOUtils.closeQuietly(resp.getOutputStream()); } catch (Exception e) { writeError(resp, e); } finally { } } /** * Writes an error to the servlet response object. * @param resp * @param error */ protected void writeError(HttpServletResponse resp, Throwable error) { try { resp.setHeader("X-Exception", error.getMessage()); //$NON-NLS-1$ resp.setStatus(500); OutputStream outputStream = null; try { outputStream = resp.getOutputStream(); PrintWriter writer = new PrintWriter(outputStream); error.printStackTrace(writer); writer.flush(); outputStream.flush(); } finally { IOUtils.closeQuietly(outputStream); } } catch (IOException e1) { throw new RuntimeException(error); } } }