/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.resiliency.spi.agent; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.model.Portlet; import com.liferay.portal.kernel.nio.intraband.RegistrationReference; import com.liferay.portal.kernel.resiliency.PortalResiliencyException; import com.liferay.portal.kernel.resiliency.spi.SPI; import com.liferay.portal.kernel.resiliency.spi.SPIConfiguration; import com.liferay.portal.kernel.resiliency.spi.agent.AcceptorServlet; import com.liferay.portal.kernel.resiliency.spi.agent.SPIAgent; import com.liferay.portal.kernel.servlet.BufferCacheServletResponse; import com.liferay.portal.kernel.servlet.ReadOnlyServletResponse; import com.liferay.portal.kernel.util.CharPool; import com.liferay.portal.kernel.util.InetAddressUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.WebKeys; import com.liferay.portal.util.PropsValues; import java.io.DataInput; import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.util.Iterator; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author Shuyang Zhou */ public class HttpClientSPIAgent implements SPIAgent { public HttpClientSPIAgent( SPIConfiguration spiConfiguration, RegistrationReference registrationReference) throws UnknownHostException { this.registrationReference = registrationReference; socketAddress = new InetSocketAddress( InetAddressUtil.getLoopbackInetAddress(), spiConfiguration.getConnectorPort()); socketBlockingQueue = new ArrayBlockingQueue<>( PropsValues.PORTAL_RESILIENCY_SPI_AGENT_CLIENT_POOL_MAX_SIZE); StringBundler sb = new StringBundler(7); sb.append("POST "); sb.append(SPI_AGENT_CONTEXT_PATH); sb.append(MAPPING_PATTERN); sb.append(" HTTP/1.1\r\nHost: localhost:"); sb.append(spiConfiguration.getConnectorPort()); sb.append("\r\n"); sb.append("Content-Length: 8\r\n\r\n"); String httpServletRequestContentString = sb.toString(); httpServletRequestContent = httpServletRequestContentString.getBytes( Charset.forName("US-ASCII")); } @Override public void destroy() { Iterator<Socket> iterator = socketBlockingQueue.iterator(); while (iterator.hasNext()) { Socket socket = iterator.next(); iterator.remove(); try { socket.close(); } catch (IOException ioe) { if (_log.isWarnEnabled()) { _log.warn(ioe, ioe); } } } } @Override public void init(SPI spi) throws PortalResiliencyException { try { SPIConfiguration spiConfiguration = spi.getSPIConfiguration(); spi.addServlet( SPI_AGENT_CONTEXT_PATH, spiConfiguration.getBaseDir(), MAPPING_PATTERN, AcceptorServlet.class.getName()); } catch (Exception e) { throw new PortalResiliencyException(e); } } @Override public HttpServletRequest prepareRequest(HttpServletRequest request) throws IOException { SPIAgentRequest spiAgentRequest = SPIAgentRequest.readFrom( request.getInputStream()); HttpServletRequest spiAgentHttpServletRequest = spiAgentRequest.populateRequest(request); spiAgentHttpServletRequest.setAttribute( WebKeys.SPI_AGENT_REQUEST, spiAgentRequest); return spiAgentHttpServletRequest; } @Override public HttpServletResponse prepareResponse( HttpServletRequest request, HttpServletResponse response) { HttpServletResponse spiAgentHttpServletResponse = new BufferCacheServletResponse( new ReadOnlyServletResponse(response)); request.setAttribute(WebKeys.SPI_AGENT_ORIGINAL_RESPONSE, response); Portlet portlet = (Portlet)request.getAttribute( WebKeys.SPI_AGENT_PORTLET); request.setAttribute( WebKeys.SPI_AGENT_RESPONSE, new SPIAgentResponse(portlet.getContextName())); return spiAgentHttpServletResponse; } @Override public void service( HttpServletRequest request, HttpServletResponse response) throws PortalResiliencyException { Socket socket = null; try { socket = borrowSocket(); SPIAgentRequest spiAgentRequest = new SPIAgentRequest(request); OutputStream outputStream = socket.getOutputStream(); outputStream.write(httpServletRequestContent); spiAgentRequest.writeTo(registrationReference, outputStream); InputStream inputStream = socket.getInputStream(); DataInputStream dataInputStream = new DataInputStream(inputStream); boolean forceCloseSocket = consumeHttpResponseHead(dataInputStream); SPIAgentResponse spiAgentResponse = SPIAgentResponse.readFrom( dataInputStream); spiAgentResponse.populate(request, response); returnSocket(socket, forceCloseSocket); socket = null; } catch (IOException ioe) { throw new PortalResiliencyException(ioe); } finally { if (socket != null) { try { socket.close(); } catch (IOException ioe) { if (_log.isWarnEnabled()) { _log.warn(ioe, ioe); } } } } } @Override public void transferResponse( HttpServletRequest request, HttpServletResponse response, Exception exception) throws IOException { SPIAgentRequest spiAgentRequest = (SPIAgentRequest)request.getAttribute( WebKeys.SPI_AGENT_REQUEST); request.removeAttribute(WebKeys.SPI_AGENT_REQUEST); File requestBodyFile = spiAgentRequest.requestBodyFile; if (requestBodyFile != null) { if (!requestBodyFile.delete()) { requestBodyFile.deleteOnExit(); } } SPIAgentResponse spiAgentResponse = (SPIAgentResponse)request.getAttribute(WebKeys.SPI_AGENT_RESPONSE); request.removeAttribute(WebKeys.SPI_AGENT_RESPONSE); if (exception != null) { spiAgentResponse.setException(exception); } else { BufferCacheServletResponse bufferCacheServletResponse = (BufferCacheServletResponse)response; spiAgentResponse.captureResponse( request, bufferCacheServletResponse); } HttpServletResponse originalResponse = (HttpServletResponse)request.getAttribute( WebKeys.SPI_AGENT_ORIGINAL_RESPONSE); request.removeAttribute(WebKeys.SPI_AGENT_ORIGINAL_RESPONSE); originalResponse.setContentLength(8); spiAgentResponse.writeTo( registrationReference, originalResponse.getOutputStream()); } protected Socket borrowSocket() throws IOException { Socket socket = socketBlockingQueue.poll(); if (socket != null) { if (socket.isClosed() || !socket.isConnected() || socket.isInputShutdown() || socket.isOutputShutdown()) { try { socket.close(); } catch (IOException ioe) { if (_log.isWarnEnabled()) { _log.warn(ioe, ioe); } } socket = null; } } if (socket == null) { socket = new Socket(); socket.connect(socketAddress); } return socket; } protected boolean consumeHttpResponseHead(DataInput dataInput) throws IOException { String statusLine = dataInput.readLine(); if (!statusLine.equals("HTTP/1.1 200 OK")) { throw new IOException("Error status line: " + statusLine); } boolean forceCloseSocket = false; String line = null; while (((line = dataInput.readLine()) != null) && (line.length() > 0)) { String[] headerKeyValuePair = StringUtil.split( line, CharPool.COLON); String headerName = headerKeyValuePair[0].trim(); headerName = StringUtil.toLowerCase(headerName); if (headerName.equals("connection")) { String headerValue = headerKeyValuePair[1].trim(); headerValue = StringUtil.toLowerCase(headerValue); if (headerValue.equals("close")) { forceCloseSocket = true; } } } return forceCloseSocket; } protected void returnSocket(Socket socket, boolean forceCloseSocket) { boolean pooled = false; if (!forceCloseSocket && socket.isConnected() && !socket.isInputShutdown() && !socket.isOutputShutdown()) { pooled = socketBlockingQueue.offer(socket); } if (!pooled) { try { socket.close(); } catch (IOException ioe) { if (_log.isWarnEnabled()) { _log.warn(ioe, ioe); } } } } protected static final String MAPPING_PATTERN = "/acceptor"; protected static final String SPI_AGENT_CONTEXT_PATH = "/spi_agent"; protected final byte[] httpServletRequestContent; protected final RegistrationReference registrationReference; protected final SocketAddress socketAddress; protected final BlockingQueue<Socket> socketBlockingQueue; private static final Log _log = LogFactoryUtil.getLog( HttpClientSPIAgent.class); }