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的问题相关推荐

  1. 原始ajax方式调用asp.net后台方法

    aspx页面: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Data.asp ...

  2. android不调用系统发送短信,android之两种方式调用短信发送接口

    释放双眼,带上耳机,听听看~! 相信很多程序员在开发程序的时候都会遇到短信调用端口的情况,今天是技术狗小编为大家带来的关于android之两种方式调用短信发送接口,希望对你学习这方面知识有帮助! an ...

  3. 设置maxJsonLength,解决ajax通过POST方式调用.net的webService时,数据过长时服务器返回500错误的问题

    设置maxJsonLength,解决ajax通过POST方式调用.net的webService时,数据过长时服务器返回500错误的问题 参考文章: (1)设置maxJsonLength,解决ajax通 ...

  4. Vue以CDN方式调用Swiper轮播异常

    Vue以CDN方式调用Swiper轮播异常 参考文章: (1)Vue以CDN方式调用Swiper轮播异常 (2)https://www.cnblogs.com/Tylerrrkd/p/9165886. ...

  5. ETH:Windows搭建ETH(区块链技术)利用Web端和小程序端两种方式调用ETH上的SC智能合约

    ETH:Windows搭建ETH(区块链技术)利用Web端和小程序端两种方式调用ETH上的SC智能合约 目录 1.Geth安装.配置文件.与ETH节点交互 1.1.下载并安装好geth客户端 1.2. ...

  6. TP的门面Facade:静态方式调用方法

    思路 Facade功能可以让类无需实例化而直接进行静态方式调用. 例子 <?php namespace app\facade;use think\Facade;class Test extend ...

  7. js方式调用php_javascript调用PHP和PHP调用javascript的方法

    javascript调用PHP和PHP调用javascript的方法 发布时间:2020-06-22 17:03:14 来源:亿速云 阅读:262 作者:Leah 这篇文章将为大家详细讲解有关java ...

  8. html ajax请求jsp,JSP+jquery使用ajax方式调用json的实现方法

    本文实例讲述了JSP+jQuery使用Ajax方式调用JSON的实现方法,在这里分享给大家以供大家参考,具体的实现以及代码如下所示: 前台: //test function test(uid) { i ...

  9. idhttp.post方式 调用datasnap rest 远程方法

    idhttp.get方式调用,这种比较简单,大家都会.post方式网上却没有任何成功的代码,本人也是摸索了一个上午才搞定. 分享给大家. (1)post方式调用的远程方法,方法名必须加"up ...

最新文章

  1. python网络爬虫教程-终于明了python网络爬虫从入门到实践
  2. 为什么基本类型可以调用方法——以字符串为例
  3. Github标星5.4k+:常见NLP模型的代码实现(基于TensorFlow和PyTorch)
  4. Android骨架屏实现方案
  5. why quantity change in item can cause CUMULAT_H changed as well
  6. BeanUtils.getProperty取得集合全部数据
  7. 计算机桌面图标有箭头,电脑桌面图标为什么会有一个小箭头,原来没的呀,
  8. 小米速度!雷军再祭 All in AIoT 大招!
  9. 《编程之美》1.3一摞烙饼的排序
  10. [渝粤教育] 西南科技大学 土力学基础工程 在线考试复习资料
  11. 支付宝支付加密规则梳理,写的太好了!
  12. 转 26款 网络会议/视频会议开源软件
  13. Python Miller Rabin 米勒-拉宾素性检验
  14. 零基础扫盲:什么是人工智能
  15. 超级壁纸android,超级壁纸大全app下载
  16. AI将带我们去何方?(中-论述篇)
  17. Modeling Personalized Item Frequency Information for Next-basket Recommendation SIGIR2020
  18. MW150UH驱动程序Linux,Ubuntu驱动Mecury MW150UH无线网卡总结
  19. 关于前端spa项目seo优化改造方案(预渲染,ssr,nuxt比较)
  20. verilog reg赋初值_Verilog语法之六:阻塞赋值与非阻塞赋值

热门文章

  1. Tomcat 7 安装成功,启动后显示空白页问题
  2. 页面上一些小icon的制作方法及技术选择
  3. 使用OpenCV开发机器视觉项目
  4. 处理过拟合问题-Regularization
  5. 模式识别新研究:微软OCR两层优化提升自然场景下的文字识别精度
  6. 基于用户投票的排名算法(四):牛顿冷却定律
  7. Deep Learning科普帖
  8. python大佬养成计划----HTML网页设计一
  9. Mysql中where条件一个单引号引发的性能损耗
  10. 彩虹背光:芝奇推出KM570 RGB机械游戏键盘