Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/com/sun/jndi/ldap/lib/BaseLdapServer.java
38867 views
/*1* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import java.io.ByteArrayOutputStream;24import java.io.Closeable;25import java.io.IOException;26import java.io.InputStream;27import java.io.OutputStream;28import java.net.InetAddress;29import java.net.ServerSocket;30import java.net.Socket;31import java.util.ArrayList;32import java.util.Arrays;33import java.util.List;34import java.util.Objects;35import java.util.concurrent.ExecutorService;36import java.util.concurrent.Executors;37import java.util.logging.*;38import static java.util.logging.Level.*;3940/*41* A bare-bones (testing aid) server for LDAP scenarios.42*43* Override the following methods to provide customized behavior44*45* * beforeAcceptingConnections46* * beforeConnectionHandled47* * handleRequest (or handleRequestEx)48*49* Instances of this class are safe for use by multiple threads.50*/51public class BaseLdapServer implements Closeable {5253private static final Logger logger = Logger.getLogger("BaseLdapServer");5455private final Thread acceptingThread = new Thread(this::acceptConnections);56private final ServerSocket serverSocket;57private final List<Socket> socketList = new ArrayList<>();58private final ExecutorService connectionsPool;5960private final Object lock = new Object();61/*62* 3-valued state to detect restarts and other programming errors.63*/64private State state = State.NEW;6566private enum State {67NEW,68STARTED,69STOPPED70}7172public BaseLdapServer() throws IOException {73this(new ServerSocket(0, 0, InetAddress.getLoopbackAddress()));74}7576public BaseLdapServer(ServerSocket serverSocket) {77this.serverSocket = Objects.requireNonNull(serverSocket);78this.connectionsPool = Executors.newCachedThreadPool();79}8081private void acceptConnections() {82logger().log(INFO, "Server is accepting connections at port {0}",83getPort());84try {85beforeAcceptingConnections();86while (isRunning()) {87Socket socket = serverSocket.accept();88logger().log(INFO, "Accepted new connection at {0}", socket);89synchronized (lock) {90// Recheck if the server is still running91// as someone has to close the `socket`92if (isRunning()) {93socketList.add(socket);94} else {95closeSilently(socket);96}97}98connectionsPool.submit(() -> handleConnection(socket));99}100} catch (Throwable t) {101if (isRunning()) {102throw new RuntimeException(103"Unexpected exception while accepting connections", t);104}105} finally {106logger().log(INFO, "Server stopped accepting connections at port {0}",107getPort());108}109}110111/*112* Called once immediately preceding the server accepting connections.113*114* Override to customize the behavior.115*/116protected void beforeAcceptingConnections() { }117118/*119* A "Template Method" describing how a connection (represented by a socket)120* is handled.121*122* The socket is closed immediately before the method returns (normally or123* abruptly).124*/125private void handleConnection(Socket socket) {126// No need to close socket's streams separately, they will be closed127// automatically when `socket.close()` is called128beforeConnectionHandled(socket);129ConnWrapper connWrapper = new ConnWrapper(socket);130try {131OutputStream out = socket.getOutputStream();132InputStream in = socket.getInputStream();133byte[] inBuffer = new byte[1024];134int count;135byte[] request;136137ByteArrayOutputStream buffer = new ByteArrayOutputStream();138int msgLen = -1;139140// As inBuffer.length > 0, at least 1 byte is read141while ((count = in.read(inBuffer)) > 0) {142buffer.write(inBuffer, 0, count);143if (msgLen <= 0) {144msgLen = LdapMessage.getMessageLength(buffer.toByteArray());145}146147if (msgLen > 0 && buffer.size() >= msgLen) {148if (buffer.size() > msgLen) {149byte[] tmpBuffer = buffer.toByteArray();150request = Arrays.copyOf(tmpBuffer, msgLen);151buffer.reset();152buffer.write(tmpBuffer, msgLen, tmpBuffer.length - msgLen);153} else {154request = buffer.toByteArray();155buffer.reset();156}157msgLen = -1;158} else {159logger.log(INFO, "Request message incomplete, " +160"bytes received {0}, expected {1}", new Integer[] { buffer.size(), msgLen});161continue;162}163handleRequestEx(socket, new LdapMessage(request), out, connWrapper);164if (connWrapper.updateRequired()) {165Socket wrapper = connWrapper.getWrapper();166in = wrapper.getInputStream();167out = wrapper.getOutputStream();168connWrapper.clearFlag();169}170}171} catch (Throwable t) {172if (!isRunning()) {173logger.log(INFO, "Connection Handler exit {0}", t.getMessage());174} else {175t.printStackTrace();176}177} finally {178if (socket != null) {179try {180socket.close();181} catch (Exception e) {182}183}184}185186if (connWrapper.getWrapper() != null) {187closeSilently(connWrapper.getWrapper());188}189}190191/*192* Called first thing in `handleConnection()`.193*194* Override to customize the behavior.195*/196protected void beforeConnectionHandled(Socket socket) { /* empty */ }197198/*199* Called after an LDAP request has been read in `handleConnection()`.200*201* Override to customize the behavior.202*/203protected void handleRequest(Socket socket,204LdapMessage request,205OutputStream out)206throws IOException207{208logger().log(INFO, "Discarding message {0} from {1}. "209+ "Override {2}.handleRequest to change this behavior.",210new Object[] {request, socket, getClass().getName()});211}212213/*214* Called after an LDAP request has been read in `handleConnection()`.215*216* Override to customize the behavior if you want to handle starttls217* extended op, otherwise override handleRequest method instead.218*219* This is extended handleRequest method which provide possibility to220* wrap current socket connection, that's necessary to handle starttls221* extended request, here is sample code about how to wrap current socket222*223* switch (request.getOperation()) {224* ......225* case EXTENDED_REQUEST:226* if (new String(request.getMessage()).endsWith(STARTTLS_REQ_OID)) {227* out.write(STARTTLS_RESPONSE);228* SSLSocket sslSocket = (SSLSocket) sslSocketFactory229* .createSocket(socket, null, socket.getLocalPort(),230* false);231* sslSocket.setUseClientMode(false);232* connWrapper.setWrapper(sslSocket);233* }234* break;235* ......236* }237*/238protected void handleRequestEx(Socket socket,239LdapMessage request,240OutputStream out,241ConnWrapper connWrapper)242throws IOException {243// by default, just call handleRequest to keep compatibility244handleRequest(socket, request, out);245}246247/*248* To be used by subclasses.249*/250protected final Logger logger() {251return logger;252}253254/*255* Starts this server. May be called only once.256*/257public BaseLdapServer start() {258synchronized (lock) {259if (state != State.NEW) {260throw new IllegalStateException(state.toString());261}262state = State.STARTED;263logger().log(INFO, "Starting server at port {0}", getPort());264acceptingThread.start();265return this;266}267}268269/*270* Stops this server.271*272* May be called at any time, even before a call to `start()`. In the latter273* case the subsequent call to `start()` will throw an exception. Repeated274* calls to this method have no effect.275*276* Stops accepting new connections, interrupts the threads serving already277* accepted connections and closes all the sockets.278*/279@Override280public void close() {281synchronized (lock) {282if (state == State.STOPPED) {283return;284}285state = State.STOPPED;286logger().log(INFO, "Stopping server at port {0}", getPort());287acceptingThread.interrupt();288closeSilently(serverSocket);289// It's important to signal an interruption so that overridden290// methods have a chance to return if they use291// interruption-sensitive blocking operations. However, blocked I/O292// operations on the socket will NOT react on that, hence the socket293// also has to be closed to propagate shutting down.294connectionsPool.shutdownNow();295socketList.forEach(BaseLdapServer.this::closeSilently);296}297}298299/**300* Returns the local port this server is listening at.301*302* This method can be called at any time.303*304* @return the port this server is listening at305*/306public int getPort() {307return serverSocket.getLocalPort();308}309310/**311* Returns the address this server is listening at.312*313* This method can be called at any time.314*315* @return the address316*/317public InetAddress getInetAddress() {318return serverSocket.getInetAddress();319}320321/*322* Returns a flag to indicate whether this server is running or not.323*324* @return {@code true} if this server is running, {@code false} otherwise.325*/326public boolean isRunning() {327synchronized (lock) {328return state == State.STARTED;329}330}331332/*333* To be used by subclasses.334*/335protected final void closeSilently(Closeable resource) {336try {337resource.close();338} catch (IOException ignored) { }339}340341/*342* To be used for handling starttls extended request343*/344protected class ConnWrapper {345private Socket original;346private Socket wrapper;347private boolean flag = false;348349public ConnWrapper(Socket socket) {350original = socket;351}352353public Socket getWrapper() {354return wrapper;355}356357public void setWrapper(Socket wrapper) {358if (wrapper != null && wrapper != original) {359this.wrapper = wrapper;360flag = true;361}362}363364public boolean updateRequired() {365return flag;366}367368public void clearFlag() {369flag = false;370}371}372}373374375