C#以post方式调用struts rest-plugin service的问题
struts2: 玩转 rest-plugin 一文中,学习了用struts2开发restful service的方法,发现用c#以post方式调用时各种报错,但java、ajax,包括firefox 的rest client插件测试也无问题。
先给出rest service中的这个方法:
1 // POST /orders 2 public HttpHeaders create() throws IOException, ServletException { 3 ordersService.doSave(model); 4 HttpServletResponse response = ServletActionContext.getResponse(); 5 HttpServletRequest request = ServletActionContext.getRequest(); 6 String ContentType = request.getHeader("Content-Type").toLowerCase(); 7 if (ContentType.startsWith("application/xml")) { // 返回xml视图 8 response.sendRedirect("orders/" + model.getId() + ".xml"); 9 } else if (ContentType.startsWith("application/json")) { // 返回json视图 10 response.sendRedirect("orders/" + model.getId() + ".json"); 11 } else {// 返回xhtml页面视图 12 response.sendRedirect("orders/"); 13 } 14 return null; 15 }
View Code
代码不复杂,post一段String过来(xml/json/html格式均可),自动映射成Order对象的实例model,然后根据请求HttpHeader中的Content-Type,如果是xml(application/xml),则返回model对应的xml,如果是json(application/json),则返回model对应的json,其它则返回页面
c#的调用代码:
1 static string PostDataByWebClient(String postUrl, String paramData, String mediaType) 2 { 3 String result = String.Empty; 4 try 5 { 6 byte[] postData = Encoding.UTF8.GetBytes(paramData); 7 WebClient webClient = new WebClient(); 8 webClient.Headers.Add("Content-Type", mediaType); 9 byte[] responseData = webClient.UploadData(new Uri(postUrl), "POST", postData); 10 result = Encoding.UTF8.GetString(responseData); 11 } 12 catch (Exception e) 13 { 14 Console.WriteLine(e); 15 result = e.Message; 16 } 17 return result; 18 } 19 20 static string PostDataByWebRequest(string postUrl, string paramData, String mediaType) 21 { 22 string result = string.Empty; 23 Stream newStream = null; 24 StreamReader sr = null; 25 HttpWebResponse response = null; 26 try 27 { 28 byte[] byteArray = Encoding.UTF8.GetBytes(paramData); 29 HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(new Uri(postUrl)); 30 webReq.Method = "POST"; 31 webReq.ContentType = mediaType; 32 webReq.ContentLength = byteArray.Length; 33 newStream = webReq.GetRequestStream(); 34 newStream.Write(byteArray, 0, byteArray.Length); 35 response = (HttpWebResponse)webReq.GetResponse(); 36 sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8); 37 result = sr.ReadToEnd(); 38 } 39 catch (Exception ex) 40 { 41 Console.WriteLine(ex); 42 result = ex.Message; 43 } 44 finally 45 { 46 if (sr != null) 47 { 48 sr.Close(); 49 } 50 if (response != null) 51 { 52 response.Close(); 53 } 54 if (newStream != null) 55 { 56 newStream.Close(); 57 } 58 } 59 return result; 60 }
View Code
这二种常用的调用方式,居然全跪了,返回的结果是一堆java异常:
java.lang.NullPointerException
at org.apache.struts2.convention.ConventionUnknownHandler.handleUnknownActionMethod(ConventionUnknownHandler.java:423)
at com.opensymphony.xwork2.DefaultUnknownHandlerManager.handleUnknownMethod(DefaultUnknownHandlerManager.java:96)
...
无奈百度了一圈,发现还有另一种方法,利用TcpClient调用
1 static string PostDataByTcpClient(string postUrl, string paramData, String mediaType) 2 { 3 String result = String.Empty; 4 TcpClient clientSocket = null; 5 Stream readStream = null; 6 try 7 { 8 clientSocket = new TcpClient(); 9 Uri URI = new Uri(postUrl); 10 clientSocket.Connect(URI.Host, URI.Port); 11 StringBuilder RequestHeaders = new StringBuilder();//用来保存HTML协议头部信息 12 RequestHeaders.AppendFormat("{0} {1} HTTP/1.1\r\n", "POST", URI.PathAndQuery); 13 RequestHeaders.AppendFormat("Connection:close\r\n"); 14 RequestHeaders.AppendFormat("Host:{0}:{1}\r\n", URI.Host,URI.Port); 15 RequestHeaders.AppendFormat("Content-Type:{0}\r\n", mediaType); 16 RequestHeaders.AppendFormat("\r\n"); 17 RequestHeaders.Append(paramData + "\r\n"); 18 Encoding encoding = Encoding.UTF8; 19 byte[] request = encoding.GetBytes(RequestHeaders.ToString()); 20 clientSocket.Client.Send(request); 21 readStream = clientSocket.GetStream(); 22 StreamReader sr = new StreamReader(readStream, Encoding.UTF8); 23 result = sr.ReadToEnd(); 24 } 25 catch (Exception e) 26 { 27 Console.WriteLine(e); 28 result = e.Message; 29 } 30 finally 31 { 32 if (readStream != null) 33 { 34 readStream.Close(); 35 } 36 if (clientSocket != null) 37 { 38 clientSocket.Close(); 39 } 40 } 41 return result; 42 }
View Code
总算调用成功了,但是由于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()方法,看下源码:
1 public ActionMapping getMapping(HttpServletRequest request, 2 ConfigurationManager configManager) { 3 ActionMapping mapping = new ActionMapping(); 4 String uri = RequestUtils.getUri(request); 5 6 uri = dropExtension(uri, mapping); 7 if (uri == null) { 8 return null; 9 } 10 11 parseNameAndNamespace(uri, mapping, configManager); 12 13 handleSpecialParameters(request, mapping); 14 15 if (mapping.getName() == null) { 16 return null; 17 } 18 19 // handle "name!method" convention. 20 handleDynamicMethodInvocation(mapping, mapping.getName()); 21 22 String fullName = mapping.getName(); 23 // Only try something if the action name is specified 24 if (fullName != null && fullName.length() > 0) { 25 26 // cut off any ;jsessionid= type appendix but allow the rails-like ;edit 27 int scPos = fullName.indexOf(';'); 28 if (scPos > -1 && !"edit".equals(fullName.substring(scPos + 1))) { 29 fullName = fullName.substring(0, scPos); 30 } 31 32 int lastSlashPos = fullName.lastIndexOf('/'); 33 String id = null; 34 if (lastSlashPos > -1) { 35 36 // fun trickery to parse 'actionName/id/methodName' in the case of 'animals/dog/edit' 37 int prevSlashPos = fullName.lastIndexOf('/', lastSlashPos - 1); 38 if (prevSlashPos > -1) { 39 mapping.setMethod(fullName.substring(lastSlashPos + 1)); 40 fullName = fullName.substring(0, lastSlashPos); 41 lastSlashPos = prevSlashPos; 42 } 43 id = fullName.substring(lastSlashPos + 1); 44 } 45 46 47 48 // If a method hasn't been explicitly named, try to guess using ReST-style patterns 49 if (mapping.getMethod() == null) { 50 51 if (isOptions(request)) { 52 mapping.setMethod(optionsMethodName); 53 54 // Handle uris with no id, possibly ending in '/' 55 } else if (lastSlashPos == -1 || lastSlashPos == fullName.length() -1) { 56 57 // Index e.g. foo 58 if (isGet(request)) { 59 mapping.setMethod(indexMethodName); 60 61 // Creating a new entry on POST e.g. foo 62 } else if (isPost(request)) { 63 if (isExpectContinue(request)) { 64 mapping.setMethod(postContinueMethodName); 65 } else { 66 mapping.setMethod(postMethodName); 67 } 68 } 69 70 // Handle uris with an id at the end 71 } else if (id != null) { 72 73 // Viewing the form to edit an item e.g. foo/1;edit 74 if (isGet(request) && id.endsWith(";edit")) { 75 id = id.substring(0, id.length() - ";edit".length()); 76 mapping.setMethod(editMethodName); 77 78 // Viewing the form to create a new item e.g. foo/new 79 } else if (isGet(request) && "new".equals(id)) { 80 mapping.setMethod(newMethodName); 81 82 // Removing an item e.g. foo/1 83 } else if (isDelete(request)) { 84 mapping.setMethod(deleteMethodName); 85 86 // Viewing an item e.g. foo/1 87 } else if (isGet(request)) { 88 mapping.setMethod(getMethodName); 89 90 // Updating an item e.g. foo/1 91 } else if (isPut(request)) { 92 if (isExpectContinue(request)) { 93 mapping.setMethod(putContinueMethodName); 94 } else { 95 mapping.setMethod(putMethodName); 96 } 97 } 98 } 99 } 100 101 // cut off the id parameter, even if a method is specified 102 if (id != null) { 103 if (!"new".equals(id)) { 104 if (mapping.getParams() == null) { 105 mapping.setParams(new HashMap()); 106 } 107 mapping.getParams().put(idParameterName, new String[]{id}); 108 } 109 fullName = fullName.substring(0, lastSlashPos); 110 } 111 112 mapping.setName(fullName); 113 return mapping; 114 } 115 // if action name isn't specified, it can be a normal request, to static resource, return null to allow handle that case 116 return null; 117 }
View Code
注意91-96行,这里有一个判断:
1 } else if (isPut(request)) { 2 if (isExpectContinue(request)) { 3 mapping.setMethod(putContinueMethodName); 4 } else { 5 mapping.setMethod(putMethodName); 6 } 7 }
再来细看下:isExpectContinue
1 protected boolean isExpectContinue(HttpServletRequest request) { 2 String expect = request.getHeader("Expect"); 3 return (expect != null && expect.toLowerCase().contains("100-continue")); 4 }
这段代码的意思是如果请求Http头里有Except信息,且等于100-continue,则返回true。如果返回true,刚才那段判断,会返回putContinueMethodName这个变量所指的方法:
1 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行为去掉
1 webReq.ServicePoint.Expect100Continue = false;//禁止自动添加Except:100-continue到http头信息
这样,最终发出去的头信息,就不会有except行
方案B: Controller中把createContinue方法补上
1 public HttpHeaders createContinue() throws IOException, ServletException{ 2 return create(); 3 }
直接调用create方法,安抚下双方,不让调用出错即可。
C#以post方式调用struts rest-plugin service的问题相关推荐
- 原始ajax方式调用asp.net后台方法
aspx页面: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Data.asp ...
- android不调用系统发送短信,android之两种方式调用短信发送接口
释放双眼,带上耳机,听听看~! 相信很多程序员在开发程序的时候都会遇到短信调用端口的情况,今天是技术狗小编为大家带来的关于android之两种方式调用短信发送接口,希望对你学习这方面知识有帮助! an ...
- 设置maxJsonLength,解决ajax通过POST方式调用.net的webService时,数据过长时服务器返回500错误的问题
设置maxJsonLength,解决ajax通过POST方式调用.net的webService时,数据过长时服务器返回500错误的问题 参考文章: (1)设置maxJsonLength,解决ajax通 ...
- Vue以CDN方式调用Swiper轮播异常
Vue以CDN方式调用Swiper轮播异常 参考文章: (1)Vue以CDN方式调用Swiper轮播异常 (2)https://www.cnblogs.com/Tylerrrkd/p/9165886. ...
- ETH:Windows搭建ETH(区块链技术)利用Web端和小程序端两种方式调用ETH上的SC智能合约
ETH:Windows搭建ETH(区块链技术)利用Web端和小程序端两种方式调用ETH上的SC智能合约 目录 1.Geth安装.配置文件.与ETH节点交互 1.1.下载并安装好geth客户端 1.2. ...
- TP的门面Facade:静态方式调用方法
思路 Facade功能可以让类无需实例化而直接进行静态方式调用. 例子 <?php namespace app\facade;use think\Facade;class Test extend ...
- js方式调用php_javascript调用PHP和PHP调用javascript的方法
javascript调用PHP和PHP调用javascript的方法 发布时间:2020-06-22 17:03:14 来源:亿速云 阅读:262 作者:Leah 这篇文章将为大家详细讲解有关java ...
- html ajax请求jsp,JSP+jquery使用ajax方式调用json的实现方法
本文实例讲述了JSP+jQuery使用Ajax方式调用JSON的实现方法,在这里分享给大家以供大家参考,具体的实现以及代码如下所示: 前台: //test function test(uid) { i ...
- idhttp.post方式 调用datasnap rest 远程方法
idhttp.get方式调用,这种比较简单,大家都会.post方式网上却没有任何成功的代码,本人也是摸索了一个上午才搞定. 分享给大家. (1)post方式调用的远程方法,方法名必须加"up ...
最新文章
- python网络爬虫教程-终于明了python网络爬虫从入门到实践
- 为什么基本类型可以调用方法——以字符串为例
- Github标星5.4k+:常见NLP模型的代码实现(基于TensorFlow和PyTorch)
- Android骨架屏实现方案
- why quantity change in item can cause CUMULAT_H changed as well
- BeanUtils.getProperty取得集合全部数据
- 计算机桌面图标有箭头,电脑桌面图标为什么会有一个小箭头,原来没的呀,
- 小米速度!雷军再祭 All in AIoT 大招!
- 《编程之美》1.3一摞烙饼的排序
- [渝粤教育] 西南科技大学 土力学基础工程 在线考试复习资料
- 支付宝支付加密规则梳理,写的太好了!
- 转 26款 网络会议/视频会议开源软件
- Python Miller Rabin 米勒-拉宾素性检验
- 零基础扫盲:什么是人工智能
- 超级壁纸android,超级壁纸大全app下载
- AI将带我们去何方?(中-论述篇)
- Modeling Personalized Item Frequency Information for Next-basket Recommendation SIGIR2020
- MW150UH驱动程序Linux,Ubuntu驱动Mecury MW150UH无线网卡总结
- 关于前端spa项目seo优化改造方案(预渲染,ssr,nuxt比较)
- verilog reg赋初值_Verilog语法之六:阻塞赋值与非阻塞赋值