File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\TcpSlaveAgentListener.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson;
25
26 import hudson.slaves.OfflineCause;
27 import jenkins.AgentProtocol;
28
29 import java.io.DataInputStream;
30 import java.io.IOException;
31 import java.io.PrintWriter;
32 import java.net.BindException;
33 import java.net.ServerSocket;
34 import java.net.Socket;
35 import java.util.logging.Level;
36 import java.util.logging.Logger;
37
38 /**
39 * Listens to incoming TCP connections from JNLP slave agents and CLI.
40 *
41 * <p>
42 * Aside from the HTTP endpoint, Jenkins runs {@link TcpSlaveAgentListener} that listens on a TCP socket.
43 * Historically this was used for inbound connection from slave agents (hence the name), but over time
44 * it was extended and made generic, so that multiple protocols of different purposes can co-exist on the
45 * same socket.
46 *
47 * <p>
48 * This class accepts the socket, then after a short handshaking, it dispatches to appropriate
49 * {@link AgentProtocol}s.
50 *
51 * @author Kohsuke Kawaguchi
52 * @see AgentProtocol
53 */
54 public final class TcpSlaveAgentListener extends Thread {
55
56 private final ServerSocket serverSocket;
57 private volatile boolean shuttingDown;
58
59 public final int configuredPort;
60
61 /**
62 * @param port
63 * Use 0 to choose a random port.
64 */
65 public TcpSlaveAgentListener(int port) throws IOException {
66 super("TCP slave agent listener port="+port);
67 try {
68 serverSocket = new ServerSocket(port);
69 } catch (BindException e) {
70 throw (BindException)new BindException("Failed to listen on port "+port+" because it's already in use.").initCause(e);
71 }
72 this.configuredPort = port;
73
74 LOGGER.info("JNLP slave agent listener started on TCP port "+getPort());
75
76 start();
77 }
78
79 /**
80 * Gets the TCP port number in which we are listening.
81 */
82 public int getPort() {
83 return serverSocket.getLocalPort();
84 }
85
86 @Override
87 public void run() {
88 try {
89 // the loop eventually terminates when the socket is closed.
90 while (true) {
91 Socket s = serverSocket.accept();
92
93 // this prevents a connection from silently terminated by the router in between or the other peer
94 // and that goes without unnoticed. However, the time out is often very long (for example 2 hours
95 // by default in Linux) that this alone is enough to prevent that.
96 s.setKeepAlive(true);
97 // we take care of buffering on our own
98 s.setTcpNoDelay(true);
99
100 new ConnectionHandler(s).start();
101 }
102 } catch (IOException e) {
103 if(!shuttingDown) {
104 LOGGER.log(Level.SEVERE,"Failed to accept JNLP slave agent connections",e);
105 }
106 }
107 }
108
109 /**
110 * Initiates the shuts down of the listener.
111 */
112 public void shutdown() {
113 shuttingDown = true;
114 try {
115 serverSocket.close();
116 } catch (IOException e) {
117 LOGGER.log(Level.WARNING, "Failed to close down TCP port",e);
118 }
119 }
120
121 private final class ConnectionHandler extends Thread {
122 private final Socket s;
123 /**
124 * Unique number to identify this connection. Used in the log.
125 */
126 private final int id;
127
128 public ConnectionHandler(Socket s) {
129 this.s = s;
130 synchronized(getClass()) {
131 id = iotaGen++;
132 }
133 setName("TCP slave agent connection handler #"+id+" with "+s.getRemoteSocketAddress());
134 }
135
136 @Override
137 public void run() {
138 try {
139 LOGGER.info("Accepted connection #"+id+" from "+s.getRemoteSocketAddress());
140
141 DataInputStream in = new DataInputStream(s.getInputStream());
142 PrintWriter out = new PrintWriter(s.getOutputStream(),true); // DEPRECATED: newer protocol shouldn't use PrintWriter but should use DataOutputStream
143
144 String s = in.readUTF();
145
146 if(s.startsWith("Protocol:")) {
147 String protocol = s.substring(9);
148 AgentProtocol p = AgentProtocol.of(protocol);
149 if (p!=null)
150 p.handle(this.s);
151 else
152 error(out, "Unknown protocol:" + s);
153 } else {
154 error(out, "Unrecognized protocol: "+s);
155 }
156 } catch (InterruptedException e) {
157 LOGGER.log(Level.WARNING,"Connection #"+id+" aborted",e);
158 try {
159 s.close();
160 } catch (IOException _) {
161 // try to clean up the socket
162 }
163 } catch (IOException e) {
164 LOGGER.log(Level.WARNING,"Connection #"+id+" failed",e);
165 try {
166 s.close();
167 } catch (IOException _) {
168 // try to clean up the socket
169 }
170 }
171 }
172
173 private void error(PrintWriter out, String msg) throws IOException {
174 out.println(msg);
175 LOGGER.log(Level.WARNING,"Connection #"+id+" is aborted: "+msg);
176 s.close();
177 }
178 }
179
180 /**
181 * Connection terminated because we are reconnected from the current peer.
182 */
183 public static class ConnectionFromCurrentPeer extends OfflineCause {
184 public String toString() {
185 return "The current peer is reconnecting";
186 }
187 }
188
189 private static int iotaGen=1;
190
191 private static final Logger LOGGER = Logger.getLogger(TcpSlaveAgentListener.class.getName());
192
193 /**
194 * Host name that we advertise the CLI client to connect to.
195 * This is primarily for those who have reverse proxies in place such that the HTTP host name
196 * and the CLI TCP/IP connection host names are different.
197 *
198 * TODO: think about how to expose this (including whether this needs to be exposed at all.)
199 */
200 public static String CLI_HOST_NAME = System.getProperty(TcpSlaveAgentListener.class.getName()+".hostName");
201 }
202
203 /*
204 Pasted from http://today.java.net/pub/a/today/2005/09/01/webstart.html
205
206 Is it unrealistic to try to control access to JWS files?
207 Is anyone doing this?
208
209 It is not unrealistic, and we are doing it. Create a protected web page
210 with a download button or link that makes a servlet call. If the user has
211 already logged in to your website, of course they can go there without
212 further authentication. The servlet reads the cookies sent by the browser
213 when the link is activated. It then generates a dynamic JNLP file adding
214 the authentication cookie and any other required cookies (JSESSIONID, etc.)
215 via <argument> tags. Write the WebStart application so that it picks up
216 any required cookies from the argument list, and adds these cookies to its
217 request headers on subsequent calls to the server. (Note: in the dynamic
218 JNLP file, do NOT put href= in the opening jnlp tag. If you do, JWS will
219 try to reload the JNLP from disk and since it's dynamic, it won't be there.
220 Leave it off and JWS will be happy.)
221
222 When returning the dynamic JNLP, the servlet should invoke setHeader(
223 "Expires", 0 ) and addDateHeader() twice on the servlet response to set
224 both "Date" and "Last-Modified" to the current date. This keeps the browser
225 from using a cached copy of a prior dynamic JNLP obtained from the same URL.
226
227 Note also that the JAR file(s) for the JWS application should not be on
228 a password-protected path - the launcher won't know about the authentication
229 cookie. But once the application starts, you can run all its requests
230 through a protected path requiring the authentication cookie, because
231 the application gets it from the dynamic JNLP. Just write it so that it
232 can't do anything useful without going through a protected path or doing
233 something to present credentials that could only have come from a valid
234 user.
235 */