C#以post方式调用struts rest-plugin service的问题
struts2: 玩转 rest-plugin一文中,学习了用struts2开发restful service的方法,发现用c#以post方式调用时各种报错,但java、ajax,包括firefox 的rest client插件测试也无问题。
先给出rest service中的这个方法:
// POST /orders
public HttpHeaders create() throws IOException, ServletException {
ordersService.doSave(model);
HttpServletResponse response = ServletActionContext.getResponse();
HttpServletRequest request = ServletActionContext.getRequest();
String ContentType = request.getHeader("Content-Type").toLowerCase();
if (ContentType.startsWith("application/xml")) { // 返回xml视图
response.sendRedirect("orders/" + model.getId() + ".xml");
} else if (ContentType.startsWith("application/json")) { // 返回json视图
response.sendRedirect("orders/" + model.getId() + ".json");
} else {// 返回xhtml页面视图
response.sendRedirect("orders/");
}
return null;
}
代码不复杂,post一段String过来(xml/json/html格式均可),自动映射成Order对象的实例model,然后根据请求HttpHeader中的Content-Type,如果是xml(application/xml),则返回model对应的xml,如果是json(application/json),则返回model对应的json,其它则返回页面
c#的调用代码:
static string PostDataByWebClient(String postUrl, String paramData, String mediaType)
{
String result = String.Empty;
try
{
byte[] postData = Encoding.UTF8.GetBytes(paramData);
WebClient webClient = new WebClient();
webClient.Headers.Add("Content-Type", mediaType);
byte[] responseData = webClient.UploadData(new Uri(postUrl), "POST", postData);
result = Encoding.UTF8.GetString(responseData);
}
catch (Exception e)
{
Console.WriteLine(e);
result = e.Message;
}
return result;
} static string PostDataByWebRequest(string postUrl, string paramData, String mediaType)
{
string result = string.Empty;
Stream newStream = null;
StreamReader sr = null;
HttpWebResponse response = null;
try
{
byte[] byteArray = Encoding.UTF8.GetBytes(paramData);
HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(new Uri(postUrl));
webReq.Method = "POST";
webReq.ContentType = mediaType;
webReq.ContentLength = byteArray.Length;
newStream = webReq.GetRequestStream();
newStream.Write(byteArray, , byteArray.Length);
response = (HttpWebResponse)webReq.GetResponse();
sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
result = sr.ReadToEnd();
}
catch (Exception ex)
{
Console.WriteLine(ex);
result = ex.Message;
}
finally
{
if (sr != null)
{
sr.Close();
}
if (response != null)
{
response.Close();
}
if (newStream != null)
{
newStream.Close();
}
}
return result;
}
这二种常用的调用方式,居然全跪了,返回的结果是一堆java异常:
java.lang.NullPointerException
at org.apache.struts2.convention.ConventionUnknownHandler.handleUnknownActionMethod(ConventionUnknownHandler.java:423)
at com.opensymphony.xwork2.DefaultUnknownHandlerManager.handleUnknownMethod(DefaultUnknownHandlerManager.java:96)
...
无奈百度了一圈,发现还有另一种方法,利用TcpClient调用
static string PostDataByTcpClient(string postUrl, string paramData, String mediaType)
{
String result = String.Empty;
TcpClient clientSocket = null;
Stream readStream = null;
try
{
clientSocket = new TcpClient();
Uri URI = new Uri(postUrl);
clientSocket.Connect(URI.Host, URI.Port);
StringBuilder RequestHeaders = new StringBuilder();//用来保存HTML协议头部信息
RequestHeaders.AppendFormat("{0} {1} HTTP/1.1\r\n", "POST", URI.PathAndQuery);
RequestHeaders.AppendFormat("Connection:close\r\n");
RequestHeaders.AppendFormat("Host:{0}:{1}\r\n", URI.Host,URI.Port);
RequestHeaders.AppendFormat("Content-Type:{0}\r\n", mediaType);
RequestHeaders.AppendFormat("\r\n");
RequestHeaders.Append(paramData + "\r\n");
Encoding encoding = Encoding.UTF8;
byte[] request = encoding.GetBytes(RequestHeaders.ToString());
clientSocket.Client.Send(request);
readStream = clientSocket.GetStream();
StreamReader sr = new StreamReader(readStream, Encoding.UTF8);
result = sr.ReadToEnd();
}
catch (Exception e)
{
Console.WriteLine(e);
result = e.Message;
}
finally
{
if (readStream != null)
{
readStream.Close();
}
if (clientSocket != null)
{
clientSocket.Close();
}
}
return result;
}
总算调用成功了,但是由于java端是用SendRedirect在客户端重定向的,所以该方法得到的返回结果如下:
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Location: http://localhost:8080/struts2-rest-ex/rest/orders/230.xml
Content-Length: 0
Date: Mon, 27 Oct 2014 03:18:56 GMT
Connection: close
是一堆http头的原文,只能曲线救国,将其中的Location:后的部分(即重定向的url),取出来再次get请求。
这样的解决方案显然有点笨拙,继续深挖:
org.apache.struts2.rest.RestActionMapper这个类的getMapping()方法,看下源码:
public ActionMapping getMapping(HttpServletRequest request,
ConfigurationManager configManager) {
ActionMapping mapping = new ActionMapping();
String uri = RequestUtils.getUri(request); uri = dropExtension(uri, mapping);
if (uri == null) {
return null;
} parseNameAndNamespace(uri, mapping, configManager); handleSpecialParameters(request, mapping); if (mapping.getName() == null) {
return null;
} // handle "name!method" convention.
handleDynamicMethodInvocation(mapping, mapping.getName()); String fullName = mapping.getName();
// Only try something if the action name is specified
if (fullName != null && fullName.length() > 0) { // cut off any ;jsessionid= type appendix but allow the rails-like ;edit
int scPos = fullName.indexOf(';');
if (scPos > -1 && !"edit".equals(fullName.substring(scPos + 1))) {
fullName = fullName.substring(0, scPos);
} int lastSlashPos = fullName.lastIndexOf('/');
String id = null;
if (lastSlashPos > -1) { // fun trickery to parse 'actionName/id/methodName' in the case of 'animals/dog/edit'
int prevSlashPos = fullName.lastIndexOf('/', lastSlashPos - 1);
if (prevSlashPos > -1) {
mapping.setMethod(fullName.substring(lastSlashPos + 1));
fullName = fullName.substring(0, lastSlashPos);
lastSlashPos = prevSlashPos;
}
id = fullName.substring(lastSlashPos + 1);
} // If a method hasn't been explicitly named, try to guess using ReST-style patterns
if (mapping.getMethod() == null) { if (isOptions(request)) {
mapping.setMethod(optionsMethodName); // Handle uris with no id, possibly ending in '/'
} else if (lastSlashPos == -1 || lastSlashPos == fullName.length() -1) { // Index e.g. foo
if (isGet(request)) {
mapping.setMethod(indexMethodName); // Creating a new entry on POST e.g. foo
} else if (isPost(request)) {
if (isExpectContinue(request)) {
mapping.setMethod(postContinueMethodName);
} else {
mapping.setMethod(postMethodName);
}
} // Handle uris with an id at the end
} else if (id != null) { // Viewing the form to edit an item e.g. foo/1;edit
if (isGet(request) && id.endsWith(";edit")) {
id = id.substring(0, id.length() - ";edit".length());
mapping.setMethod(editMethodName); // Viewing the form to create a new item e.g. foo/new
} else if (isGet(request) && "new".equals(id)) {
mapping.setMethod(newMethodName); // Removing an item e.g. foo/1
} else if (isDelete(request)) {
mapping.setMethod(deleteMethodName); // Viewing an item e.g. foo/1
} else if (isGet(request)) {
mapping.setMethod(getMethodName); // Updating an item e.g. foo/1
} else if (isPut(request)) {
if (isExpectContinue(request)) {
mapping.setMethod(putContinueMethodName);
} else {
mapping.setMethod(putMethodName);
}
}
}
} // cut off the id parameter, even if a method is specified
if (id != null) {
if (!"new".equals(id)) {
if (mapping.getParams() == null) {
mapping.setParams(new HashMap());
}
mapping.getParams().put(idParameterName, new String[]{id});
}
fullName = fullName.substring(0, lastSlashPos);
} mapping.setName(fullName);
return mapping;
}
// if action name isn't specified, it can be a normal request, to static resource, return null to allow handle that case
return null;
}
注意91-96行,这里有一个判断:
} else if (isPut(request)) {
if (isExpectContinue(request)) {
mapping.setMethod(putContinueMethodName);
} else {
mapping.setMethod(putMethodName);
}
}
再来细看下:isExpectContinue
protected boolean isExpectContinue(HttpServletRequest request) {
String expect = request.getHeader("Expect");
return (expect != null && expect.toLowerCase().contains("100-continue"));
}
这段代码的意思是如果请求Http头里有Except信息,且等于100-continue,则返回true。如果返回true,刚才那段判断,会返回putContinueMethodName这个变量所指的方法:
private String postContinueMethodName = "createContinue";
但是Controller里只有create方法,并没有createContinue方法,所以找不到方法,当然报错。
而c#中如果以post方法请求url时,不论是HttpWebRequest还是WebClient,默认都会添加expect = 100-continue的头信息,因此c#调用时会报错,而firefox的RestClient插件、java调用、ajax调用,因为没有拼except信息,不会出错。
那么except = 100-continue是什么东西呢?为何c#要自动拼这上这行头信息?可以参见园友的文章:http之100-continue,大意是说:
如果客户端向服务端post数据,考虑到post的数据可能很大,搞不好能把服务器玩坏(或者超时),所以,有一个贴心的约定,客户端先发一个except头信息给服务器,问下:我要post数据了,可能很大,你想想要不要收,采用什么措施收?如果服务器很聪明,可能会对这种情况做出特殊响应,就比如刚才的java代码,遇到这种头信息,不是调用create方法,而是createContinue方法。
这本是一个不错的约定,但是偏偏本文中的Controller方法,又没有提供createContinue方法,所以辜负了客户端的美意,好心当成驴肝肺了。
终极解决方案:
方案A:HttpWebRequest请求时,把默认的except行为去掉
webReq.ServicePoint.Expect100Continue = false;//禁止自动添加Except:100-continue到http头信息
这样,最终发出去的头信息,就不会有except行
方案B: Controller中把createContinue方法补上
public HttpHeaders createContinue() throws IOException, ServletException{
return create();
}
直接调用create方法,安抚下双方,不让调用出错即可。
C#以post方式调用struts rest-plugin service的问题的更多相关文章
- 用HTTP方式调用gearman任务处理
本来以为是个挺美好的东西,结果... 这样的方式非常不安全,尤其是假设暴露在公网地址,非常easy被攻击,并且gearman的http服务远没有专业的webserver健壮. 攻击方式非常easy:t ...
- struts2 2.5.16 通配符方式调用action中的方法报404
1.问题描述 在struts.xml中配置用通配符方式调用action中的add()方法,访问 http://localhost:8080/Struts2Demo/helloworld_add.act ...
- Atitit 动态调用webservice与客户端代理方式调用
Atitit 动态调用webservice与客户端代理方式调用 方式1: 使用call.invoke 直接调用WSDL,缺点:麻烦,不推荐--特别是JAVA调用.NET的WS时,会有不少的问题需要解 ...
- YbSoftwareFactory 代码生成插件【二十五】:Razor视图中以全局方式调用后台方法输出页面代码的三种方法
上一篇介绍了 MVC中实现动态自定义路由 的实现,本篇将介绍Razor视图中以全局方式调用后台方法输出页面代码的三种方法. 框架最新的升级实现了一个页面部件功能,其实就是通过后台方法查询数据库内容,把 ...
- HttpClient Get/Post方式调用Http接口
本节摘要:本节主要分别介绍如何用get方式.post方式向http接口发送数据. preparation 1. 项目环境如下: myeclipse6.5 .tomcat5.0.system:xp.JD ...
- 反射-优化及程序集等(用委托的方式调用需要反射调用的方法(或者属性、字段),而不去使用Invoke方法)
反射-优化及程序集等(用委托的方式调用需要反射调用的方法(或者属性.字段),而不去使用Invoke方法) 创建Delegate (1).Delegate.CreateDelegate(Type, ...
- Matlab与C/C++联合编程之Matlab以MEX方式调用C代码(五)完整过程加示
如下为本人亲证代码: 一: 编译器的安装与配置(环境不同,显示结果不同) 要使用MATLAB编译器,用户计算机上应用事先安装与MATLAB适配的以下任何一种ANSI C/C++编译器: 5.0.6.0 ...
- JS方式调用本地的可执行文件
看到一个方法,有些用,先存下来,有用的时候再用. 前几天,在IE,FIREFOX中实现了用JS方式调用本地的可执行文件.地址:www.yihaomen.com/article/js/211.htm , ...
- AutoCAD.NET 不使用P/Invoke方式调用acad.exe或accore.dll中的接口(如acedCommand、acedPostCommand等)
使用C#进行AutoCAD二次开发,有时候由于C#接口不够完善,或者低版本AutoCAD中的接口缺少,有些工作不能直接通过C#接口来实现,所以需要通过P/Invoke的方式调用AutoCAD的其他DL ...
随机推荐
- Linux 折腾汇集,实时更新
一.Linux教程 入门教程:http://www.92csz.com/study/linux/ 命令大全:http://man.linuxde.net/ 一.界面: 在Ubuntu.Linux Mi ...
- Nginx 多站点配置
最近学习和练习的时候,为Laravel应用程序添加了好几个站点,有些程序删除之后站点却还留着,这让强迫症感到非常难受,上次解决了这个问题之后并没有记录一下,于是导致今天又花了很多时间折腾,所以特地来写 ...
- ADO.NET知识汇总
这又是一篇记录平常工作笔记的博客,无论是在排版还是解说上都不会有太多要求.同时这也是一篇不上博客园首页的博客,Just记录一些工作笔记. vSelect返回单个值 string connSQL = @ ...
- MySQL用户无法登陆问题
安装完MySQL后,我们通常添加拥有相应权限的普通用户用来访问数据库.在使用普通用户(假设为tom)本地登录数据库的时候,经常会出现无法登录的情况,但是从其他的mysql客户端却可以登录.在本地使用t ...
- 烂泥:学习mysql的binlog配置
本文由秀依林枫提供友情赞助,首发于烂泥行天下. 1.基础知识 日志是把数据库的每一个变化都记载到一个专用的文件里,这种文件就叫做日志文件.mysql默认只开启错误日志,因为过多的日志将会影响系统的处理 ...
- iNeedle产品介绍
一.产品简介 1.产品背景 1.您曾经遇到过下面的问题和烦恼吗?2.当网站上线以后,如何实时的了解网站的运行状况?3.当网站访问速度慢,是升级服务器?还是升级带宽?还是优化网站代码?4.当网站新上线一 ...
- (转)android.intent.action.MAIN与android.intent.category.LAUNCHER
android.intent.action.MAIN决定应用程序最先启动的Activity android.intent.category.LAUNCHER决定应用程序是否显示在程序列表里 在网上看到 ...
- 利用apply和arguments复用方法
首先,有个单例对象,它上面挂了很多静态工具方法.其中有一个是each,用来遍历数组或对象. var nativeForEach = [].forEach var nativeMap = [].map ...
- Python引用模块和查找模块路径
模块间相互独立相互引用是任何一种编程语言的基础能力.对于"模块"这个词在各种编程语言中或许是不同的,但我们可以简单认为一个程序文件是一个模块,文件里包含了类或者方法的定义.对于编译 ...
- ZIP打包解包
linux zip命令的基本用法是: zip [参数] [打包后的文件名] [打包的目录路径] linux zip命令参数列表: -a 将文件转成ASCII模式-F 尝试修复损坏的压缩文件-h 显示帮 ...