1.HttpClient使用时,最好让httpclient可以重用,可以创建一个HttpClient对象,然后在同步代码块中使用,或者使用连接池管理(推荐)。
2.HttpClient虽然可以重用,但是PostMethod(或GetMethod)不能重用,否则会出问题(类似总是Read timeout等)。
各种版本代码:
1.完整正确的代码(使用了连接池,并且每次连接都重新创建PostMethod对象,PostMethod对象使用完后关闭。测试没发生任何问题):
package com.handmessage.ups.shortmessageapi.send;
import java.io.IOException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import com.handmessage.ups.shortmessageapi.ShortMessageApiService;
import com.handmessage.ups.shortmessageapi.bean.ShortMessage;
public class SendCaptchaQueen {
private static Logger logger = LogManager.getLogger(SendCaptchaQueen.class);
static int requestTimeout = 10000;
static int readTimeout = 15000;
static HttpClient client;
static {
HttpClientParams httpParameters = new HttpClientParams();
httpParameters.setConnectionManagerTimeout(requestTimeout);
httpParameters.setSoTimeout(readTimeout);
httpParameters.setContentCharset(ShortMessageApiService.getEncode());
MultiThreadedHttpConnectionManager hm = new MultiThreadedHttpConnectionManager();
client = new HttpClient(httpParameters, hm);
}
public static boolean sendCaptcha(ShortMessage sm) {
logger.info(sm.getContent());
PostMethod method = new PostMethod(ShortMessageApiService.getUrl());
method.setRequestHeader("ContentType","application/x-www-form-urlencoded;charset=" + ShortMessageApiService.getEncode());
NameValuePair[] data = {
new NameValuePair("account", ShortMessageApiService.getAccount()),
new NameValuePair("password", ShortMessageApiService.getPassword()), //密码可以使用明文密码或使用32位MD5加密
new NameValuePair("mobile", sm.getMobileNo()),
new NameValuePair("content", sm.getContent()),
};
String submitResult, code = null, message;
try {
method.setRequestBody(data);
int httpCode = client.executeMethod(method);
switch (httpCode) {
case HttpStatus.SC_INTERNAL_SERVER_ERROR:
case HttpStatus.SC_NOT_FOUND:
case HttpStatus.SC_UNAUTHORIZED:
case HttpStatus.SC_PRECONDITION_FAILED:
default:
logger.error("短信请求失败,httpCode:{}。手机号:{},用户名:{},短信内容:{}",
httpCode, sm.getMobileNo(), ShortMessageApiService.getAccount(), sm.getContent());
break;
case HttpStatus.SC_OK:
submitResult = method.getResponseBodyAsString();
Document doc = DocumentHelper.parseText(submitResult);
Element root = doc.getRootElement();
code = root.elementText("code");
message = root.elementText("msg");
String smsid = root.elementText("smsid");
if ("2".equals(code))
logger.info("短信发送成功,手机号:{},smsid:{}", sm.getMobileNo(), smsid);
else
logger.warn("短信发送失败,手机号:{},code:{},msg:{},用户名:{},短信内容:{}",
sm.getMobileNo(), code, message, ShortMessageApiService.getAccount(), sm.getContent());
break;
}
} catch (IOException | DocumentException e) {
logger.error("短信发送失败,代码出现异常:{}", e);
} finally {
if(method != null)
try {
method.getResponseBodyAsStream().close();
} catch (IOException e) {
logger.error("PostMethod关闭出现异常:{}", e);
}
}
return "2".equals(code);
}
}
2.使用连接池,并且每次重新创建PostMethod对象,使用完PostMethod位关闭(10个线程测试没啥问题,性能也和上一种差不多)
package com.handmessage.ups.shortmessageapi.send;
import java.io.IOException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import com.handmessage.ups.shortmessageapi.ShortMessageApiService;
import com.handmessage.ups.shortmessageapi.bean.ShortMessage;
public class SendCaptchaQueen {
private static Logger logger = LogManager.getLogger(SendCaptchaQueen.class);
static HttpClient client;
static {
client = new HttpClient();
client.getParams().setContentCharset(ShortMessageApiService.getEncode());
client.getParams().setConnectionManagerTimeout(10000);
client.getParams().setSoTimeout(15000);
}
public static boolean sendCaptcha(ShortMessage sm) {
logger.info(sm.getContent());
NameValuePair[] data = {
new NameValuePair("account", ShortMessageApiService.getAccount()),
new NameValuePair("password", ShortMessageApiService.getPassword()), //密码可以使用明文密码或使用32位MD5加密
new NameValuePair("mobile", sm.getMobileNo()),
new NameValuePair("content", sm.getContent()),
};
String submitResult, code = null, message;
try {
PostMethod method = new PostMethod(ShortMessageApiService.getUrl());
method.setRequestHeader("ContentType","application/x-www-form-urlencoded;charset=" + ShortMessageApiService.getEncode());
method.setRequestBody(data);
synchronized (SendCaptchaQueen.class) {
client.executeMethod(method);
submitResult = method.getResponseBodyAsString();
}
Document doc = DocumentHelper.parseText(submitResult);
Element root = doc.getRootElement();
code = root.elementText("code");
message = root.elementText("msg");
String smsid = root.elementText("smsid");
if ("2".equals(code))
logger.info("短信发送成功,手机号:{},smsid:{}", sm.getMobileNo(), smsid);
else
logger.warn("短信发送失败,手机号:{},code:{},msg:{},短信内容:{}", sm.getMobileNo(), code, message,sm.getContent());
} catch (IOException | DocumentException e) {
logger.error("电话:{},内容:{},异常:{}", sm.getMobileNo(), sm.getContent(), e);
}
return "2".equals(code);
}
}
3.未使用连接池,并且每次重新创建PostMethod对象,使用完PostMethod未关闭(10个线程测试没啥问题,性能也和上一种差不多):
package com.handmessage.ups.shortmessageapi.send;
import java.io.IOException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import com.handmessage.ups.shortmessageapi.ShortMessageApiService;
import com.handmessage.ups.shortmessageapi.bean.ShortMessage;
public class SendCaptchaQueen {
private static Logger logger = LogManager.getLogger(SendCaptchaQueen.class);
static HttpClient client;
static {
client = new HttpClient();
client.getParams().setContentCharset(ShortMessageApiService.getEncode());
client.getParams().setConnectionManagerTimeout(10000);
client.getParams().setSoTimeout(15000);
}
public static boolean sendCaptcha(ShortMessage sm) {
logger.info(sm.getContent());
NameValuePair[] data = {
new NameValuePair("account", ShortMessageApiService.getAccount()),
new NameValuePair("password", ShortMessageApiService.getPassword()), //密码可以使用明文密码或使用32位MD5加密
new NameValuePair("mobile", sm.getMobileNo()),
new NameValuePair("content", sm.getContent()),
};
String submitResult, code = null, message;
try {
PostMethod method = new PostMethod(ShortMessageApiService.getUrl());
method.setRequestHeader("ContentType","application/x-www-form-urlencoded;charset=" + ShortMessageApiService.getEncode());
method.setRequestBody(data);
synchronized (SendCaptchaQueen.class) {
client.executeMethod(method);
submitResult = method.getResponseBodyAsString();
}
Document doc = DocumentHelper.parseText(submitResult);
Element root = doc.getRootElement();
code = root.elementText("code");
message = root.elementText("msg");
String smsid = root.elementText("smsid");
if ("2".equals(code))
logger.info("短信发送成功,手机号:{},smsid:{}", sm.getMobileNo(), smsid);
else
logger.warn("短信发送失败,手机号:{},code:{},msg:{},短信内容:{}", sm.getMobileNo(), code, message,sm.getContent());
} catch (IOException | DocumentException e) {
logger.error("电话:{},内容:{},异常:{}", sm.getMobileNo(), sm.getContent(), e);
}
return "2".equals(code);
}
}
4.使用了连接池,并且公用了PostMethod对象。(测试10个线程并发,除了第一个执行成功,其他均报read timeout错误):
package com.handmessage.ups.shortmessageapi.send;
import java.io.IOException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import com.handmessage.ups.shortmessageapi.ShortMessageApiService;
import com.handmessage.ups.shortmessageapi.bean.ShortMessage;
public class SendCaptchaQueen {
private static Logger logger = LogManager.getLogger(SendCaptchaQueen.class);
static HttpClient client;
static PostMethod method;
static {
HttpClientParams httpParameters = new HttpClientParams();
httpParameters.setConnectionManagerTimeout(10000);
httpParameters.setSoTimeout(15000);
httpParameters.setContentCharset(ShortMessageApiService.getEncode());
MultiThreadedHttpConnectionManager hm = new MultiThreadedHttpConnectionManager();
client = new HttpClient(httpParameters, hm);
method = new PostMethod(ShortMessageApiService.getUrl());
method.setRequestHeader("ContentType","application/x-www-form-urlencoded;charset=" + ShortMessageApiService.getEncode());
}
public static boolean sendCaptcha(ShortMessage sm) {
logger.info(sm.getContent());
NameValuePair[] data = {
new NameValuePair("account", ShortMessageApiService.getAccount()),
new NameValuePair("password", ShortMessageApiService.getPassword()), //密码可以使用明文密码或使用32位MD5加密
new NameValuePair("mobile", sm.getMobileNo()),
new NameValuePair("content", sm.getContent()),
};
String submitResult, code = null, message;
try {
synchronized (SendCaptchaQueen.class) {
method.setRequestBody(data);
client.executeMethod(method);
submitResult = method.getResponseBodyAsString();
}
Document doc = DocumentHelper.parseText(submitResult);
Element root = doc.getRootElement();
code = root.elementText("code");
message = root.elementText("msg");
String smsid = root.elementText("smsid");
if ("2".equals(code))
logger.info("短信发送成功,手机号:{},smsid:{}", sm.getMobileNo(), smsid);
else
logger.warn("短信发送失败,手机号:{},code:{},msg:{},短信内容:{}", sm.getMobileNo(), code, message,sm.getContent());
} catch (IOException | DocumentException e) {
logger.error("电话:{},内容:{},异常:{}", sm.getMobileNo(), sm.getContent(), e);
}
return "2".equals(code);
}
}
5.未使用连接池并公用PostMethod对象(现象和上一种差不多):
package com.handmessage.ups.shortmessageapi.send;
import java.io.IOException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import com.handmessage.ups.shortmessageapi.ShortMessageApiService;
import com.handmessage.ups.shortmessageapi.bean.ShortMessage;
public class SendCaptchaQueen {
private static Logger logger = LogManager.getLogger(SendCaptchaQueen.class);
static HttpClient client;
static PostMethod method;
static {
client = new HttpClient();
client.getParams().setContentCharset(ShortMessageApiService.getEncode());
client.getParams().setConnectionManagerTimeout(10000);
client.getParams().setSoTimeout(15000);
method = new PostMethod(ShortMessageApiService.getUrl());
method.setRequestHeader("ContentType","application/x-www-form-urlencoded;charset=" + ShortMessageApiService.getEncode());
}
public static boolean sendCaptcha(ShortMessage sm) {
logger.info(sm.getContent());
NameValuePair[] data = {
new NameValuePair("account", ShortMessageApiService.getAccount()),
new NameValuePair("password", ShortMessageApiService.getPassword()), //密码可以使用明文密码或使用32位MD5加密
new NameValuePair("mobile", sm.getMobileNo()),
new NameValuePair("content", sm.getContent()),
};
String submitResult, code = null, message;
try {
synchronized (SendCaptchaQueen.class) {
method.setRequestBody(data);
client.executeMethod(method);
submitResult = method.getResponseBodyAsString();
}
Document doc = DocumentHelper.parseText(submitResult);
Element root = doc.getRootElement();
code = root.elementText("code");
message = root.elementText("msg");
String smsid = root.elementText("smsid");
if ("2".equals(code))
logger.info("短信发送成功,手机号:{},smsid:{}", sm.getMobileNo(), smsid);
else
logger.warn("短信发送失败,手机号:{},code:{},msg:{}", sm.getMobileNo(), code, message);
} catch (IOException | DocumentException e) {
logger.error(e);
}
return "2".equals(code);
}
}
Junit测试代码如下:
package com.handmessage.ups.shortmessageapi.client;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.handmessage.jeelib.apisocket.client.ApiClient;
import com.handmessage.ups.shortmessageapi.client.SendCaptchaClient.RegisterSendRequest;
import com.handmessage.ups.shortmessageapi.client.SendCaptchaClient.SendResponse;
import com.handmessage.ups.shortmessageapi.client.SendSmsClient.SendSmsRequest;
import com.handmessage.ups.shortmessageapi.client.SendSmsClient.SendSmsResponse;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring-test-config.xml"})
public class SendMessageConcurrentTest {
private static ObjectMapper mapper = new ObjectMapper();
@Autowired
private ApiClient shortMessageApi;
Random random = new Random();
CountDownLatch begin = new CountDownLatch(1);
CountDownLatch end = new CountDownLatch(10);
@Test
public void test() {
for (int i = 0; i < 10; i++) {
new Thread(new MyThread("thread" + (i + 1))).start();
}
begin.countDown();
try {
end.await();
System.out.println("完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class MyThread implements Runnable {
public MyThread() {}
public MyThread(String name) {
this.name = name;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void run() {
try {
String phone = getPhone();
boolean go = random.nextBoolean();
begin.await();
try {
go(go, phone);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
end.countDown();
}
}
public void go(boolean go, String phone) throws JsonProcessingException {
if (go) {
postReg(phone);
System.out.println("postReg" + phone);
} else {
postQuik();
System.out.println("postQuik" + phone);
}
}
public void postReg(String phone) throws JsonProcessingException {
RegisterSendRequest request = new RegisterSendRequest(phone, null, 3, "127.0.0.1");
SendResponse response = (SendResponse) shortMessageApi.getResponse(request);
System.out.println(this.getName() + ":" + mapper.writeValueAsString(response));
}
public void postQuik() throws JsonProcessingException {
SendSmsRequest request = new SendSmsRequest();
request.setMobilePhone(getPhone());
request.setName("test" + this.getName());
request.setPw("pass" + this.getName());
SendSmsResponse response = (SendSmsResponse) shortMessageApi.getResponse(request);
System.out.println(this.getName() + ":" + mapper.writeValueAsString(response));
}
public String getPhone() {
String[] phones = {"测试电话号码1", "测试电话号码2", "测试电话号码3"};
return phones[random.nextInt(3)];
}
}
}