zoukankan      html  css  js  c++  java
  • 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
    ", "POST", URI.PathAndQuery);
    13         RequestHeaders.AppendFormat("Connection:close
    ");
    14         RequestHeaders.AppendFormat("Host:{0}:{1}
    ", URI.Host,URI.Port);
    15         RequestHeaders.AppendFormat("Content-Type:{0}
    ", mediaType);
    16         RequestHeaders.AppendFormat("
    ");
    17         RequestHeaders.Append(paramData + "
    ");
    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方法,安抚下双方,不让调用出错即可。

  • 相关阅读:
    1509 -- Glass Beads POJ
    1043: [HAOI2008]下落的圆盘
    面向对象的特性一:封装性
    java的值传递机制
    可变参数的形参方法
    方法的重载
    类及对象
    面向对象
    数组的常用算法问题
    数组的常见异常
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/4053784.html
Copyright © 2011-2022 走看看