This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.catalina.ha.backend;
/* for MBean to read ready and busy */
import java.util.Set;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import org.apache.tomcat.util.modeler.Registry;
/*
* Listener to provider informations to mod_heartbeat.c
* *msg_format = "v=%u&ready=%u&busy=%u"; (message to send).
* send the multicast message using the format...
* what about the bind(IP. port) only IP makes sense (for the moment).
* BTW:v = version :-)
*/
public class CollectedInfo {
/* Collect info via JMX */
protected MBeanServer mBeanServer = null;
protected ObjectName objName = null;
int ready;
int busy;
int port = 0;
String host = null;
public CollectedInfo(String host, int port) throws Exception {
init(host, port);
}
public void init(String host, int port) throws Exception {
int iport = 0;
String shost = null;
mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
String onStr = "*:type=ThreadPool,*";
ObjectName objectName = new ObjectName(onStr);
Set<ObjectInstance> set = mBeanServer.queryMBeans(objectName, null);
for (ObjectInstance oi : set) {
objName = oi.getObjectName();
String name = objName.getKeyProperty("name");
/* Name are:
* http-8080
* jk-10.33.144.3-8009
* jk-jfcpc%2F10.33.144.3-8009
*/
String [] elenames = name.split("-");
String sport = elenames[elenames.length-1];
iport = Integer.parseInt(sport);
String [] shosts = elenames[1].split("%2F");
shost = shosts[0];
if (port==0 && host==null)
break; /* Take the first one */
if (host==null && iport==port)
break; /* Only port done */
if (shost.compareTo(host) == 0)
break; /* Done port and host are the expected ones */
}
if (objName == null)
throw new Exception("Can't find connector for " + host + ":" + port);
this.port = iport;
this.host = shost;
}
public void refresh() throws Exception {
if (mBeanServer == null || objName == null) {
throw new Exception("Not initialized!!!");
}
Integer imax = (Integer) mBeanServer.getAttribute(objName, "maxThreads");
// the currentThreadCount could be 0 before the threads are created...
// Integer iready = (Integer) mBeanServer.getAttribute(objName, "currentThreadCount");
Integer ibusy = (Integer) mBeanServer.getAttribute(objName, "currentThreadsBusy");
busy = ibusy.intValue();
ready = imax.intValue() - ibusy.intValue();
}
}

View File

@@ -0,0 +1,125 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.catalina.ha.backend;
import org.apache.catalina.ContainerEvent;
import org.apache.catalina.ContainerListener;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/*
* Listener to provider informations to mod_heartbeat.c
* *msg_format = "v=%u&ready=%u&busy=%u"; (message to send).
* send the multicast message using the format...
* what about the bind(IP. port) only IP makes sense (for the moment).
* BTW:v = version :-)
*/
public class HeartbeatListener implements LifecycleListener, ContainerListener {
private static final Log log = LogFactory.getLog(HeartbeatListener.class);
/* To allow to select the connector */
private int port = 0;
private String host = null;
/* for multicasting stuff */
private final String ip = "224.0.1.105"; /* Multicast IP */
private final int multiport = 23364; /* Multicast Port */
private final int ttl = 16;
public String getHost() { return host; }
public String getGroup() { return ip; }
public int getMultiport() { return multiport; }
public int getTtl() { return ttl; }
/**
* Proxy list, format "address:port,address:port".
*/
private final String proxyList = null;
public String getProxyList() { return proxyList; }
/**
* URL prefix.
*/
private final String proxyURL = "/HeartbeatListener";
public String getProxyURL() { return proxyURL; }
private CollectedInfo coll = null;
private Sender sender = null;
@Override
public void containerEvent(ContainerEvent event) {
}
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (Lifecycle.PERIODIC_EVENT.equals(event.getType())) {
if (sender == null) {
if (proxyList == null)
sender = new MultiCastSender();
else
sender = new TcpSender();
}
/* Read busy and ready */
if (coll == null) {
try {
coll = new CollectedInfo(host, port);
this.port = coll.port;
this.host = coll.host;
} catch (Exception ex) {
log.error("Unable to initialize info collection: " + ex);
coll = null;
return;
}
}
/* Start or restart sender */
try {
sender.init(this);
} catch (Exception ex) {
log.error("Unable to initialize Sender: " + ex);
sender = null;
return;
}
/* refresh the connector information and send it */
try {
coll.refresh();
} catch (Exception ex) {
log.error("Unable to collect load information: " + ex);
coll = null;
return;
}
String output = "v=1&ready=" + coll.ready + "&busy=" + coll.busy +
"&port=" + port;
try {
sender.send(output);
} catch (Exception ex) {
log.error("Unable to send collected load information: " + ex);
}
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.catalina.ha.backend;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.nio.charset.StandardCharsets;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/*
* Sender to proxies using multicast socket.
*/
public class MultiCastSender
implements Sender {
private static final Log log = LogFactory.getLog(HeartbeatListener.class);
HeartbeatListener config = null;
/* for multicasting stuff */
MulticastSocket s = null;
InetAddress group = null;
@Override
public void init(HeartbeatListener config) throws Exception {
this.config = config;
}
@Override
public int send(String mess) throws Exception {
if (s == null) {
try {
group = InetAddress.getByName(config.getGroup());
if (config.getHost() != null) {
InetAddress addr = InetAddress.getByName(config.getHost());
InetSocketAddress addrs = new InetSocketAddress(addr, config.getMultiport());
s = new MulticastSocket(addrs);
} else
s = new MulticastSocket(config.getMultiport());
s.setTimeToLive(config.getTtl());
s.joinGroup(group);
} catch (Exception ex) {
log.error("Unable to use multicast: " + ex);
s = null;
return -1;
}
}
byte[] buf;
buf = mess.getBytes(StandardCharsets.US_ASCII);
DatagramPacket data = new DatagramPacket(buf, buf.length, group, config.getMultiport());
try {
s.send(data);
} catch (Exception ex) {
log.error("Unable to send collected load information: " + ex);
s.close();
s = null;
return -1;
}
return 0;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.catalina.ha.backend;
import java.net.InetAddress;
/*
* This class represents a front-end httpd server.
*
*/
public class Proxy {
public InetAddress address = null;
public int port = 80;
}

View File

@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.catalina.ha.backend;
/**
* Interface to send data to proxies.
*/
public interface Sender {
/**
* Set the configuration parameters
* @param config The heartbeat listener configuration
* @throws Exception An error occurred
*/
public void init(HeartbeatListener config) throws Exception;
/**
* Send the message to the proxies
* @param mess The message that will be sent
* @return <code>0</code> if no error occurred, <code>-1</code> otherwise
* @throws Exception An error occurred
*/
public int send(String mess) throws Exception;
}

View File

@@ -0,0 +1,208 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.catalina.ha.backend;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.StringTokenizer;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/*
* Sender to proxies using multicast socket.
*/
public class TcpSender
implements Sender {
private static final Log log = LogFactory.getLog(HeartbeatListener.class);
HeartbeatListener config = null;
/**
* Proxies.
*/
protected Proxy[] proxies = null;
/**
* Active connections.
*/
protected Socket[] connections = null;
protected BufferedReader[] connectionReaders = null;
protected BufferedWriter[] connectionWriters = null;
@Override
public void init(HeartbeatListener config) throws Exception {
this.config = config;
StringTokenizer tok = new StringTokenizer(config.getProxyList(), ",");
proxies = new Proxy[tok.countTokens()];
int i = 0;
while (tok.hasMoreTokens()) {
String token = tok.nextToken().trim();
int pos = token.indexOf(':');
if (pos <=0)
throw new Exception("bad ProxyList");
proxies[i] = new Proxy();
proxies[i].port = Integer.parseInt(token.substring(pos + 1));
try {
proxies[i].address = InetAddress.getByName(token.substring(0, pos));
} catch (Exception e) {
throw new Exception("bad ProxyList");
}
i++;
}
connections = new Socket[proxies.length];
connectionReaders = new BufferedReader[proxies.length];
connectionWriters = new BufferedWriter[proxies.length];
}
@Override
public int send(String mess) throws Exception {
if (connections == null) {
log.error("Not initialized");
return -1;
}
String requestLine = "POST " + config.getProxyURL() + " HTTP/1.0";
for (int i = 0; i < connections.length; i++) {
if (connections[i] == null) {
try {
if (config.getHost() != null) {
connections[i] = new Socket();
InetAddress addr = InetAddress.getByName(config.getHost());
InetSocketAddress addrs = new InetSocketAddress(addr, 0);
connections[i].setReuseAddress(true);
connections[i].bind(addrs);
addrs = new InetSocketAddress(proxies[i].address, proxies[i].port);
connections[i].connect(addrs);
} else
connections[i] = new Socket(proxies[i].address, proxies[i].port);
connectionReaders[i] = new BufferedReader(new InputStreamReader(connections[i].getInputStream()));
connectionWriters[i] = new BufferedWriter(new OutputStreamWriter(connections[i].getOutputStream()));
} catch (Exception ex) {
log.error("Unable to connect to proxy: " + ex);
close(i);
}
}
if (connections[i] == null)
continue; // try next proxy in the list
BufferedWriter writer = connectionWriters[i];
try {
writer.write(requestLine);
writer.write("\r\n");
writer.write("Content-Length: " + mess.length() + "\r\n");
writer.write("User-Agent: HeartbeatListener/1.0\r\n");
writer.write("Connection: Keep-Alive\r\n");
writer.write("\r\n");
writer.write(mess);
writer.write("\r\n");
writer.flush();
} catch (Exception ex) {
log.error("Unable to send collected load information to proxy: " + ex);
close(i);
}
if (connections[i] == null)
continue; // try next proxy in the list
/* Read httpd answer */
String responseStatus = connectionReaders[i].readLine();
if (responseStatus == null) {
log.error("Unable to read response from proxy");
close(i);
continue;
} else {
responseStatus = responseStatus.substring(responseStatus.indexOf(' ') + 1, responseStatus.indexOf(' ', responseStatus.indexOf(' ') + 1));
int status = Integer.parseInt(responseStatus);
if (status != 200) {
log.error("Status is " + status);
close(i);
continue;
}
// read all the headers.
String header = connectionReaders[i].readLine();
int contentLength = 0;
while (!"".equals(header)) {
int colon = header.indexOf(':');
String headerName = header.substring(0, colon).trim();
String headerValue = header.substring(colon + 1).trim();
if ("content-length".equalsIgnoreCase(headerName)) {
contentLength = Integer.parseInt(headerValue);
}
header = connectionReaders[i].readLine();
}
if (contentLength > 0) {
char[] buf = new char[512];
while (contentLength > 0) {
int thisTime = (contentLength > buf.length) ? buf.length : contentLength;
int n = connectionReaders[i].read(buf, 0, thisTime);
if (n <= 0) {
log.error("Read content failed");
close(i);
break;
} else {
contentLength -= n;
}
}
}
}
}
return 0;
}
/**
* Close connection.
* @param i The index of the connection that will be closed
*/
protected void close(int i) {
try {
if (connectionReaders[i] != null) {
connectionReaders[i].close();
}
} catch (IOException e) {
}
connectionReaders[i] = null;
try {
if (connectionWriters[i] != null) {
connectionWriters[i].close();
}
} catch (IOException e) {
}
connectionWriters[i] = null;
try {
if (connections[i] != null) {
connections[i].close();
}
} catch (IOException e) {
}
connections[i] = null;
}
}