Thrift除了可以通过TCP协议访问,还可以通过HTTP/HTTPS协议访问,在java中,thrift提供了一个servlet:org.apache.thrift.server.TServlet,我们只需继承这个TServlet就可以很方便的将TCP服务转换成HTTP/HTTPS服务,参考http://hanqunfeng.iteye.com/blog/1936556,为其中的ContractManage服务提供servlet接口,如下:
一。基本实现方法
1)服务端
package servlet; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.server.TServlet; import thrift.service.ContactManager; import thrift.service.impl.ContactManagerImpl; @SuppressWarnings("serial") public class ContractManageServlet extends TServlet { @SuppressWarnings({ "unchecked", "rawtypes" }) public ContractManageServlet(ContactManagerImpl contractManage) { super(new ContactManager.Processor(contractManage), new TCompactProtocol.Factory()); } }
只需要改写构造函数即可,将实现类通过参数的形式传递给父类。
通过spring对servlet的管理(参考:http://hanqunfeng.iteye.com/blog/605174)来注入参数:
<!-- servlet适配器,这里必须明确声明,因为spring默认没有初始化该适配器 --> <bean id="servletHandlerAdapter" class="org.springframework.web.servlet.handler.SimpleServletHandlerAdapter"/> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"> <property name="order" value="2"></property> </bean> <!-- servlet --> <bean name="/contractManageServlet.do" class="servlet.ContractManageServlet"> <constructor-arg> <ref bean="contactManagerImpl"/> </constructor-arg> </bean>
2)客户端
模拟一个客户端调用:
package servlet; import org.apache.http.HttpVersion; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.BasicClientConnectionManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.HttpConnectionParams; import org.apache.thrift.TException; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.THttpClient; import org.apache.thrift.transport.TTransportException; import thrift.service.ContactManager; public class ContractMain { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub String url = "http://localhost:8080/ThriftServer/contractManageServlet.do"; BasicHttpParams params = new BasicHttpParams(); params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); params.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, "UTF-8"); // Disable Expect-Continue params.setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false); // Enable staleness check params.setParameter("http.connection.stalecheck", true); HttpConnectionParams.setSoTimeout(params, 10000); // 10 secondes HttpConnectionParams.setConnectionTimeout(params, 10000); // 10 secondes SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("http", 8080, PlainSocketFactory .getSocketFactory())); schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory .getSocketFactory())); BasicClientConnectionManager cm = new BasicClientConnectionManager( schemeRegistry); THttpClient thc; try { thc = new THttpClient(url, new DefaultHttpClient(cm, params)); TProtocol loPFactory = new TCompactProtocol(thc); ContactManager.Client client = new ContactManager.Client(loPFactory); System.out.println(client.getAll()); } catch (TTransportException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (TException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
OK,每增加一个thrift服务只需要写一个servlet即可,不过每个servlet只是存在一个构造函数,每次都要增加一个类略显繁琐,所以可以使用一个代理类来实现。
二。代理实现方法
1)服务端
代理类copy了org.apache.thrift.server.TServlet的代码,并且增加了两个构造函数,使其可以按需创建对象。
public class ThriftServletProxy extends HttpServlet { ………………copy……………… @SuppressWarnings({ "rawtypes", "unchecked" }) public ThriftServletProxy(String serviceInterface, String serviceIface, Object serviceImplObject) throws Exception { super(); Class Processor = Class.forName(serviceInterface + "$Processor"); Class Iface = Class .forName(StringUtils.hasText(serviceIface) ? serviceIface : serviceInterface + "$Iface"); Constructor con = Processor.getConstructor(Iface); TProcessor processor = (TProcessor) con.newInstance(serviceImplObject); this.processor = processor; this.inProtocolFactory = new TCompactProtocol.Factory(); this.outProtocolFactory = new TCompactProtocol.Factory(); this.customHeaders = new ArrayList<Map.Entry<String, String>>(); } public ThriftServletProxy(String serviceInterface, Object serviceImplObject) throws Exception { this(serviceInterface,null,serviceImplObject); } ………………copy…………………… }
这样,每增加一个服务只需要在spring配置文件中增加配置即可,不需要再单独创建一个servlet:
<!-- servlet适配器,这里必须明确声明,因为spring默认没有初始化该适配器 --> <bean id="servletHandlerAdapter" class="org.springframework.web.servlet.handler.SimpleServletHandlerAdapter"/> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"> <property name="order" value="2"></property> </bean> <!-- servlet proxy --> <bean name="/contractManageServletProxy.do" class="servlet.ThriftServletProxy"> <constructor-arg index="0" value="thrift.service.ContactManager"/> <constructor-arg index="1" value="interfaceI.ContractManage.Iface"/> <constructor-arg> <ref bean="contactManagerImpl"/> </constructor-arg> </bean> <bean name="/userServiceServletProxy.do" class="servlet.ThriftServletProxy"> <constructor-arg index="0" value="thrift.service.UserService"/> <constructor-arg index="1" value="interfaceI.Userservice.Iface"/> <constructor-arg> <ref bean="userServiceImpl"/> </constructor-arg> </bean>
2)客户端
客户端同样采用代理的方式来实现
package thrift.proxy; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import org.apache.http.HttpVersion; import org.apache.http.conn.params.ConnManagerParams; import org.apache.http.conn.params.ConnPerRouteBean; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.BasicClientConnectionManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.HttpConnectionParams; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.THttpClient; import org.apache.thrift.transport.TTransportException; @SuppressWarnings("deprecation") public class ThriftServletClientProxy { /** * servlet 地址 */ private String servletUrl; public String getServletUrl() { return servletUrl; } public void setServletUrl(String servletUrl) { this.servletUrl = servletUrl; } /** * thrift 接口 */ private String serviceInterface; public String getServiceInterface() { return serviceInterface; } public void setServiceInterface(String serviceInterface) { this.serviceInterface = serviceInterface; } private static BasicHttpParams params; static { params = new BasicHttpParams(); params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); params.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, "UTF-8"); // Disable Expect-Continue params.setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false); // Enable staleness check params.setParameter("http.connection.stalecheck", true); HttpConnectionParams.setSoTimeout(params, 10000); // 10 secondes HttpConnectionParams.setConnectionTimeout(params, 10000); // 10 secondes ConnManagerParams.setMaxTotalConnections(params, 20); ConnPerRouteBean connPerRoute = new ConnPerRouteBean(20); ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute); } @SuppressWarnings({ "rawtypes", "unchecked" }) public Object getClient() { Object object = null; try { BasicClientConnectionManager cm = new BasicClientConnectionManager( getSchemeRegistry()); THttpClient thc = new THttpClient(getServletUrl(), new DefaultHttpClient(cm, params)); TProtocol loPFactory = new TCompactProtocol(thc); Class client = Class.forName(getServiceInterface() + "$Client"); Constructor con = client.getConstructor(TProtocol.class); object = con.newInstance(loPFactory); } catch (TTransportException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } return object; } public SchemeRegistry getSchemeRegistry() { SchemeRegistry schemeRegistry = new SchemeRegistry(); URL url; try { // 分析url,取出端口 url = new URL(getServletUrl()); String protocol = url.getProtocol(); int port = url.getPort(); if (-1 == port) { if ("https".equals(protocol)) { port = 443; } else { port = 80; } } if ("https".equals(protocol)) { schemeRegistry.register(new Scheme("https", port, SSLSocketFactory.getSocketFactory())); } else { schemeRegistry.register(new Scheme("http", port, PlainSocketFactory.getSocketFactory())); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return schemeRegistry; } }
每调用一个接口,在spring中增加一个配置即可:
<bean id="contractManage" class="thrift.proxy.ThriftServletClientProxy"> <property name="servletUrl"> <value> http://localhost:8080/ThriftServer/contractManageServletProxy.do </value> </property> <property name="serviceInterface" value="thrift.service.ContactManager"> </property> </bean> <bean id="userService" class="thrift.proxy.ThriftServletClientProxy"> <property name="servletUrl"> <value> http://localhost:8080/ThriftServer/userServiceServletProxy.do </value> </property> <property name="serviceInterface" value="thrift.service.UserService"> </property> </bean>
调用方式参考代码中的IndexController。