This article was last updated: 2017-11-27 Personal blog address: blog.sqdyy.cn
introduce
When I was an intern at the Postal Service, the leader asked me to read the source code of Tomcat and try to implement the function of remote deployment project by myself, which led to this practice.
In Tomact, there is a Manager application, which is used to manage the deployed Web application. In this application, ManagerServlet is the main servlet, which can obtain part of tomcat metrics and remotely manage the Web application. However, this functionality is protected by security constraints in web application deployment.
When you request the ManagerServlet, it checks the value returned by getPathInfo() and the associated query parameters to determine the requested operation. It supports the following operations and parameters (starting with the servlet path):
Request path | describe |
---|---|
/deploy? config={config-url} | Deploy and start a new Web application according to the specified path (see source code) |
/deploy? config={config-url}&war={war-url}/ | Deploy and start a new Web application based on the specified PAT (see source code) |
/deploy? path=/xxx&war={war-url} | Deploy and start a new Web application according to the specified path (see source code) |
/list | Lists the context paths of all Web applications. Format: PATH: Status: Sessions (number of active sessions) |
/reload? path=/xxx | Reloads the Web application at the specified path |
/resources? type=xxxx | Enumerates available global JNDI resources to restrict the specified Java class name |
/serverinfo | Displays system information and JVM information |
/sessions | |
/expire? path=/xxx | List session idle time of web applications in path |
/expire? path=/xxx&idle=mm | Expire sessions for the context path /xxx which were idle for at least mm minutes. |
/sslConnectorCiphers | Display the diagnostic information about the SSL/TLS password configured for the connector |
/start? path=/xx | Starts the Web application at the specified path |
/stop? path=/xxx | Closes the Web application at the specified path |
/threaddump | Write a JVM thread dump |
/undeploy? path=/xxx | Close and delete the Web application at the specified path, then delete the underlying WAR file or document base directory. |
We can remotely deploy our projects to the server using the operations provided by getPathInfo() in the ManagerServlet. I’ll post my code for the practice below. To do this, you only need to introduce the HttpClient and Commons packages.
Encapsulates a unified remote request management class
Encapsulate this class to make it easy for a client to request a ManagerServlet:
import java.io.File;
import java.net.URL;
import java.net.URLEncoder;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.protocol.BasicHttpContext;
public class TomcatManager {
private static final String MANAGER_CHARSET = "UTF-8";
private String username;
private URL url;
private String password;
private String charset;
private boolean verbose;
private DefaultHttpClient httpClient;
private BasicHttpContext localContext;
/** constructor */
public TomcatManager(URL url, String username) {
this(url, username, "");
}
public TomcatManager(URL url, String username, String password) {
this(url, username, password, "ISO-8859-1");
}
public TomcatManager(URL url, String username, String password, String charset) {
this(url, username, password, charset, true);
}
public TomcatManager(URL url, String username, String password, String charset, boolean verbose) {
this.url = url;
this.username = username;
this.password = password;
this.charset = charset;
this.verbose = verbose;
// Create the network request configuration
PoolingClientConnectionManager poolingClientConnectionManager = new PoolingClientConnectionManager();
poolingClientConnectionManager.setMaxTotal(5);
this.httpClient = new DefaultHttpClient(poolingClientConnectionManager);
if (StringUtils.isNotEmpty(username)) {
Credentials creds = new UsernamePasswordCredentials(username, password);
String host = url.getHost();
int port = url.getPort() > -1 ? url.getPort() : AuthScope.ANY_PORT;
httpClient.getCredentialsProvider().setCredentials(new AuthScope(host, port), creds);
AuthCache authCache = new BasicAuthCache();
BasicScheme basicAuth = new BasicScheme();
HttpHost targetHost = new HttpHost(url.getHost(), url.getPort(), url.getProtocol());
authCache.put(targetHost, basicAuth);
localContext = newBasicHttpContext(); localContext.setAttribute(ClientContext.AUTH_CACHE, authCache); }}/** Deploy and start a new application with the specified path */
public TomcatManagerResponse deploy(String path, File war, boolean update) throws Exception {
StringBuilder buffer = new StringBuilder("/deploy");
buffer.append("? path=").append(URLEncoder.encode(path, charset));
if(war ! =null) {
buffer.append("&war=").append(URLEncoder.encode(war.toString(), charset));
}
if (update) {
buffer.append("&update=true");
}
return invoke(buffer.toString());
}
/** Get the context path for all deployed Web applications. The format is PATH: STATUS: Sessions (number of active sessions) */
public TomcatManagerResponse list(a) throws Exception {
StringBuilder buffer = new StringBuilder("/list");
return invoke(buffer.toString());
}
/** Get system information and JVM information */
public TomcatManagerResponse serverinfo(a) throws Exception {
StringBuilder buffer = new StringBuilder("/serverinfo");
return invoke(buffer.toString());
}
/** How to actually send the request */
private TomcatManagerResponse invoke(String path) throws Exception {
HttpRequestBase httpRequestBase = new HttpGet(url + path);
HttpResponse response = httpClient.execute(httpRequestBase, localContext);
int statusCode = response.getStatusLine().getStatusCode();
switch (statusCode) {
case HttpStatus.SC_OK: / / 200
case HttpStatus.SC_CREATED: / / 201
case HttpStatus.SC_ACCEPTED: / / 202
break;
case HttpStatus.SC_MOVED_PERMANENTLY: / / 301
case HttpStatus.SC_MOVED_TEMPORARILY: / / 302
case HttpStatus.SC_SEE_OTHER: / / 303
String redirectUrl = getRedirectUrl(response);
this.url = new URL(redirectUrl);
return invoke(path);
}
return new TomcatManagerResponse().setStatusCode(response.getStatusLine().getStatusCode())
.setReasonPhrase(response.getStatusLine().getReasonPhrase())
.setHttpResponseBody(IOUtils.toString(response.getEntity().getContent()));
}
/** Extract the redirected URL */
protected String getRedirectUrl(HttpResponse response) {
Header locationHeader = response.getFirstHeader("Location");
String locationField = locationHeader.getValue();
// is it a relative Location or a full ?
return locationField.startsWith("http")? locationField : url.toString() +'/'+ locationField; }}Copy the code
Encapsulate the response result set
@Data
public class TomcatManagerResponse {
private int statusCode;
private String reasonPhrase;
private String httpResponseBody;
}
Copy the code
Testing remote deployment
Prior to testing, please enable the following user permissions in the configuration file:
<role rolename="admin-gui"/>
<role rolename="admin-script"/>
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="manager-jmx"/>
<role rolename="manager-status"/>
<user username="sqdyy" password="123456" roles="manager-gui,manager-script,manager-jmx,manager-status,admin-script,admin-gui"/>
Copy the code
Here is the code to test a successful remote deployment of a WAR package:
import static org.testng.AssertJUnit.assertEquals;
import java.io.File;
import java.net.URL;
import org.testng.annotations.Test;
public class TestTomcatManager {
@Test
public void testDeploy(a) throws Exception {
TomcatManager tm = new TomcatManager(new URL("http://localhost:8080/manager/text"), "sqdyy"."123456");
File war = new File("E: \ \ tomcat \ \ simple - war - project - 1.0 - the SNAPSHOT. War");
TomcatManagerResponse response = tm.deploy("/ simple - war - project - 1.0 - the SNAPSHOT", war, true);
System.out.println(response.getHttpResponseBody());
assertEquals(200, response.getStatusCode());
// output:
// Deployed ok-Deployed Application at context path/simpe-war-project-1.0-snapshot
}
@Test
public void testList(a) throws Exception {
TomcatManager tm = new TomcatManager(new URL("http://localhost:8080/manager/text"), "sqdyy"."123456");
TomcatManagerResponse response = tm.list();
System.out.println(response.getHttpResponseBody());
assertEquals(200, response.getStatusCode());
// output:
// OK - Listed applications for virtual host localhost
// /:running:0:ROOT
/ / / simple - war - project - 1.0 - the SNAPSHOT: running: 0: simple - war - project - 1.0 - the SNAPSHOT
// /examples:running:0:examples
// /host-manager:running:0:host-manager
// /manager:running:0:manager
// /docs:running:0:docs
}
@Test
public void testServerinfo(a) throws Exception {
TomcatManager tm = new TomcatManager(new URL("http://localhost:8080/manager/text"), "sqdyy"."123456");
TomcatManagerResponse response = tm.serverinfo();
System.out.println(response.getHttpResponseBody());
assertEquals(200, response.getStatusCode());
// output:
// OK - Server info
// Tomcat Version: Apache Tomcat/7.0.82
// OS Name: Windows 10
/ / OS Version: 10.0
// OS Architecture: amd64
/ / the JVM Version: 1.8.0 comes with _144 - b01
// JVM Vendor: Oracle Corporation}}Copy the code
The resources
Source address of ManagerServlet