/** * Copyright 2013 Netflix, 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 com.netflix.suro; import com.google.common.io.Closeables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * A simple blocking control server that processes user-sent command */ public class SuroControl { private static final Logger log = LoggerFactory.getLogger(SuroControl.class); public void start(int port) throws IOException { ServerSocket serverSocket; try { serverSocket = new ServerSocket(port); log.info("Suro control service started at port " + port); } catch (IOException e) { throw new IOException(String.format("Can't start server socket at port %d for Suro's control service: %s", port, e.getMessage()), e); } try{ while (true) { Socket clientSocket = null; try{ clientSocket = listen(port, serverSocket); Command cmd = processCommand(clientSocket); if(cmd == Command.EXIT) { return; } }finally { closeAndIgnoreError(clientSocket); } } }finally { closeAndIgnoreError(serverSocket); log.info("Suro's control service exited"); } } private Socket listen(int port, ServerSocket serverSocket) throws IOException { try { return serverSocket.accept(); }catch(IOException e) { throw new IOException(String.format("Error when Suro control was accepting user connection at port %d: %s", port, e.getMessage()), e); } } /** * Processes user command. For now it supports only "exit", case insensitive. * @param clientSocket The client socket after connection is established between a client and this server * * @return the last processed command */ private Command processCommand(Socket clientSocket) { BufferedReader in = null; BufferedWriter out = null; try{ in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())); String s; while ((s = in.readLine()) != null) { if("exit".equalsIgnoreCase(s.trim())) { respond(out, "ok"); return Command.EXIT; }else { respond(out, String.format("Unknown command '%s'", s)); } } }catch (IOException e) { log.warn(String.format("Failed to accept user command at port %d: %s", clientSocket.getPort(), e.getMessage()), e); }finally { try{ Closeables.close(in, true); Closeables.close(out, true); } catch(IOException ignored) { } } return null; } // Implemented this method for both Socket and ServerSocket because we want to // make Suro compilable and runnable under Java 6. private static void closeAndIgnoreError(Socket socket) { if(socket == null) return; try{ socket.close(); }catch(IOException e) { log.warn(String.format("Failed to close the client socket on port %d: %s. Exception ignored.", socket.getPort(), e.getMessage()), e); } } private static void closeAndIgnoreError(ServerSocket socket) { if(socket == null) return; try{ socket.close(); }catch(IOException e) { log.warn(String.format("Failed to close the server socket on port %d: %s. Exception ignored.", socket.getLocalPort(), e.getMessage()), e); } } /** * Writes line-based response. * @param out the channel used to write back response * @param response A string that ends with a new line * @throws IOException */ private void respond(BufferedWriter out, String response) throws IOException { out.append(response); out.append("\n"); out.flush(); } private static enum Command { EXIT("exit") ; private final String name; private Command(String name) { this.name = name; } public String getName() { return name; } } public static void main(String[] args) throws Exception { SuroControl control = new SuroControl(); control.start(8080); } }