如何从服务器端模拟复杂的 REST 调用?

发布于 2024-12-12 07:00:23 字数 375 浏览 2 评论 0原文

在使用广泛使用 REST 服务的 javascript 时——包括使用 GET、PUT、POST、DELETES 等词汇;我发现很难模拟服务器端,因此前端开发可以独立(后端)进行。

有时捕获多步骤数据也很有用,因此我们甚至可以帮助重现整个 REST 链(或从这些链触发的与前端相关的错误)

我可以使用哪些工具来模拟 REST 调用,尤其是有状态的调用那些? (即,如果我对某些资源执行 PUT,我希望下一个 GET 会以某种方式改变)

我尝试了 SOAPUI 4.0.1,它的 REST 模拟令人失望。另外,我的需求超出了单一状态模拟(任何人都可以使用静态 .json 文件来完成)。我需要进行状态转换类型的模拟;使用 Content-Range 标头是最好的。

有人吗?

While working with javascript that uses REST services extensively -- including using vocabs like GET, PUT, POST, DELETES, etc; I have found it hard to mock the server side so front end development can go on independently (of back end).

It is also useful to sometimes capture multi-step data, so we can help reproduce the entire chain of REST even (or bugs related to the front end that are triggered from these chains)

What tools can I use to mock REST calls, esp stateful ones? (i.e. if I do a PUT on some resource, I expect the next GET on it to change somehow)

I tried SOAPUI 4.0.1 and it's REST mocking is disappointing. Plus, my need is beyond single state mocking (which anyone can do with a static .json file). I need to do state transition type of mocks; working with Content-Range headers would be best.

Anyone?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

遇到 2024-12-19 07:00:24

实际上,我最终创建了自己的 Java REST 模拟引擎,它基本上可以模拟任何响应。只要您可以手工制作或剪切粘贴模拟整个 http 响应的文本文件,您就可以使用我的解决方案来模拟该服务。

这是 servlet:

package com.mockrest.debug;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class MockGridData
 */
public class MockRest extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public MockRest() {
        super();
        // TODO Auto-generated constructor stub
    }

    @Override
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException {
        sub:{
            HttpServletRequest request = (HttpServletRequest)req;
            HttpServletResponse response = (HttpServletResponse)res;
            String setdata = request.getParameter("__setdata");
            if (setdata!=null && setdata.length()>0){
                System.err.println("Setting Data...");
                HttpSession sess = request.getSession(true);
                String data = "/"+request.getParameter("__setdata");
                sess.setAttribute("data", data);
                try{
                    InputStream is = getServletContext().getResourceAsStream(data);
                    if (is!=null){
                        is.close();
                        response.getWriter().write("Successfully pointed next REST call to:"+data);
                    }
                    else{
                        response.sendError(500, "Cannot find resource:"+data);
                    }
                }
                catch (IOException ioe){
                    response.sendError(500, Arrays.deepToString(ioe.getStackTrace()));
                }

            }
            else{
                System.err.println("Fetching Data...");
                HttpSession sess = request.getSession(false);
                if (sess==null || sess.getAttribute("data")==null){
                    response.sendError(500,"Session invalid or no Previous Data Set!");
                }
                String rsrc = (String)sess.getAttribute("data");
                System.err.println("Resource Being used:"+rsrc);
                InputStream is = getServletContext().getResourceAsStream(rsrc);
                if (is!=null){
                    String statusline = readLine(is);
                    Pattern statusPat = Pattern.compile("^HTTP/1.1 ([0-9]+) (.*)$");
                    Matcher m = statusPat.matcher(statusline);
                    if (m!=null && m.matches()){
                        int status = Integer.valueOf(m.group(1));
                        response.setStatus(status, m.group(2));
                    }
                    else{
                        throw new ServletException("Bad input file: status line parsing failed, got this as status line:"+statusline);
                    }
                    String line;
                    Pattern httpHeaderPat = Pattern.compile("^([^:]+): (.*)$");
                    while ((line=readLine(is))!=null){
                        if (line.length()==0){
                            // end of headers
                            break;
                        }
                        Matcher m2 = httpHeaderPat.matcher(line);
                        if (m2!=null && m2.matches()){
                            response.setHeader(m2.group(1), m2.group(2));
                        }
                    }
                    OutputStream os = response.getOutputStream();
                    byte[] buf = new byte[1024];
                    int size;
                    while ((size=is.read(buf))>0){
                        os.write(buf, 0, size);
                    }
                    os.flush();
                }
            }
        }
    }

    private String readLine(InputStream is) throws IOException {
        StringBuffer sb = new StringBuffer();
        char c;
        while ((c=(char)is.read())!='\n'){
            sb.append(c);
        }
        if (sb.charAt(sb.length()-1) == '\r'){
            sb.deleteCharAt(sb.length()-1);
        }
        return sb.toString();
    }

}

要配置它,请将预构建的响应文件放入 WebContent 文件夹中。我通常以 .http 扩展名结束这些文件。

下面是一个 init.http 文件示例。假设我们将此文件放置在 WebContent 内名为 data 的文件夹中:

HTTP/1.1 200 OK
Date: Wed, 26 Oct 2011 18:31:45 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 4.0.30319
Content-Range: items 0-1/2
Content-Length: 385
Cache-Control: private
Content-Type: application/json

[
  {
    "id": "249F0",
    "field1": " Global",
    "displaystartdate": "2007-10-20",
    "displayenddate": "2012-10-20",
    "status": "Major Delay",
    "children": true
  },
  {
    "id": "962581",
    "field2": "Europe",
    "displaystartdate": "2007-10-20",
    "displayenddate": "2012-10-20",
    "status": "Major Delay",
    "children": true
  }
]

标头必须与正文用空行分隔(没有空格,nada)。熟悉 http 的人会注意到这是一个纯粹的 http 响应。这是故意的。

您可以使用此工具来模拟您希望响应具有的任何 http 标头;甚至用不同的服务器标头进行响应(在我的示例中,我模拟了假装是 IIS 6.0 的响应);或不同的 HTTP 状态代码等。

从您的浏览器/javascript 调用它;首先用:

http://yourserver/yourweb/MockGridData?__setdata=data/init.http

一起使用

http://yourserver/yourweb/MockGridData

然后在您的 javascript 或 REST AJAX 调用中,如果它与任何方法或参数 ;它将获得您之前制作的 http 响应;甚至细化到内容范围;缓存标头;等等。如果您需要后续 AJAX 调用返回其他内容,只需再次使用 __setdata 进行调用即可。我建议您设置一些按钮来在您的网络应用程序中进行显式状态转换。

假设一切都已设置完毕,对于模拟的 REST 链,开发人员可以执行以下操作:

  1. 调用

    http://yourserver/yourweb/MockGridData?__setdata=data/init.http
    
  2. 运行一个 javascript 模块,该模块将导致调用(例如,使用 GET)

    http://yourserver/yourweb/MockGridData
    
  3. 单击一个按钮,然后执行以下操作:

    http://yourserver/yourweb/MockGridData?__setdata=data/step1.http
    
  4. 运行另一个将导致调用的 javascript 步骤(例如,使用 PUT)

    http://yourserver/yourweb/MockGridData
    
  5. 单击另一个按钮,然后执行以下操作:

    http://yourserver/yourweb/MockGridData?__setdata=data/step2.http
    
  6. 运行另一个将导致调用的 javascript 步骤(例如,使用 GET)

    http://yourserver/yourweb/MockGridData
    

    但这次期望的结果与 #4 不同。

这甚至应该适用于二进制和压缩响应,但我还没有测试过。

I actually ended up creating my own Java REST Mock Engine that can basically mock any response. As long as you can handcraft or cut-paste a text file that simulates the entire http response, you can use my solution to mock the service.

Here's the servlet:

package com.mockrest.debug;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class MockGridData
 */
public class MockRest extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public MockRest() {
        super();
        // TODO Auto-generated constructor stub
    }

    @Override
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException {
        sub:{
            HttpServletRequest request = (HttpServletRequest)req;
            HttpServletResponse response = (HttpServletResponse)res;
            String setdata = request.getParameter("__setdata");
            if (setdata!=null && setdata.length()>0){
                System.err.println("Setting Data...");
                HttpSession sess = request.getSession(true);
                String data = "/"+request.getParameter("__setdata");
                sess.setAttribute("data", data);
                try{
                    InputStream is = getServletContext().getResourceAsStream(data);
                    if (is!=null){
                        is.close();
                        response.getWriter().write("Successfully pointed next REST call to:"+data);
                    }
                    else{
                        response.sendError(500, "Cannot find resource:"+data);
                    }
                }
                catch (IOException ioe){
                    response.sendError(500, Arrays.deepToString(ioe.getStackTrace()));
                }

            }
            else{
                System.err.println("Fetching Data...");
                HttpSession sess = request.getSession(false);
                if (sess==null || sess.getAttribute("data")==null){
                    response.sendError(500,"Session invalid or no Previous Data Set!");
                }
                String rsrc = (String)sess.getAttribute("data");
                System.err.println("Resource Being used:"+rsrc);
                InputStream is = getServletContext().getResourceAsStream(rsrc);
                if (is!=null){
                    String statusline = readLine(is);
                    Pattern statusPat = Pattern.compile("^HTTP/1.1 ([0-9]+) (.*)$");
                    Matcher m = statusPat.matcher(statusline);
                    if (m!=null && m.matches()){
                        int status = Integer.valueOf(m.group(1));
                        response.setStatus(status, m.group(2));
                    }
                    else{
                        throw new ServletException("Bad input file: status line parsing failed, got this as status line:"+statusline);
                    }
                    String line;
                    Pattern httpHeaderPat = Pattern.compile("^([^:]+): (.*)$");
                    while ((line=readLine(is))!=null){
                        if (line.length()==0){
                            // end of headers
                            break;
                        }
                        Matcher m2 = httpHeaderPat.matcher(line);
                        if (m2!=null && m2.matches()){
                            response.setHeader(m2.group(1), m2.group(2));
                        }
                    }
                    OutputStream os = response.getOutputStream();
                    byte[] buf = new byte[1024];
                    int size;
                    while ((size=is.read(buf))>0){
                        os.write(buf, 0, size);
                    }
                    os.flush();
                }
            }
        }
    }

    private String readLine(InputStream is) throws IOException {
        StringBuffer sb = new StringBuffer();
        char c;
        while ((c=(char)is.read())!='\n'){
            sb.append(c);
        }
        if (sb.charAt(sb.length()-1) == '\r'){
            sb.deleteCharAt(sb.length()-1);
        }
        return sb.toString();
    }

}

To configure it, place prebuilt response files inside your WebContent folder. I usually end these files with .http extensions.

An example init.http file is below. Pretend we placed this file inside a folder called data inside WebContent:

HTTP/1.1 200 OK
Date: Wed, 26 Oct 2011 18:31:45 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 4.0.30319
Content-Range: items 0-1/2
Content-Length: 385
Cache-Control: private
Content-Type: application/json

[
  {
    "id": "249F0",
    "field1": " Global",
    "displaystartdate": "2007-10-20",
    "displayenddate": "2012-10-20",
    "status": "Major Delay",
    "children": true
  },
  {
    "id": "962581",
    "field2": "Europe",
    "displaystartdate": "2007-10-20",
    "displayenddate": "2012-10-20",
    "status": "Major Delay",
    "children": true
  }
]

Headers must separate with body by an empty line (no spaces, nada). People familiar with http will notice it's a pure http response. This is on purpose.

You can use this tool to simulate any of the http headers you want the response to have; even going so far to respond with different server header(in my example, I simulated the response pretending to be IIS 6.0); or a different HTTP status code, etc.

To invoke it from your browser/javascript; first prime it with:

http://yourserver/yourweb/MockGridData?__setdata=data/init.http

Then in your javascript or REST AJAX call, if it goes to

http://yourserver/yourweb/MockGridData

with any method or parameter; it will get the http response you previously crafted with; even down to the Content-Range; Cache headers; etc. If you then need the subsequent AJAX call to return something else, simply call with __setdata again. I suggest you setup a few buttons to do the explicit state transition in your web app.

Assuming everything is setup, for a simulated REST chain, a developer may do:

  1. invoke

    http://yourserver/yourweb/MockGridData?__setdata=data/init.http
    
  2. run a javascript module that will result in calling (say, with GET)

    http://yourserver/yourweb/MockGridData
    
  3. click a button that then does:

    http://yourserver/yourweb/MockGridData?__setdata=data/step1.http
    
  4. run another javascript step that will result in calling (say, with PUT)

    http://yourserver/yourweb/MockGridData
    
  5. click another button that then does:

    http://yourserver/yourweb/MockGridData?__setdata=data/step2.http
    
  6. run another javascript step that will result in calling (say, with GET)

    http://yourserver/yourweb/MockGridData
    

    but this time expecting different result than #4.

This should even work with binary and gzipped responses, but I haven't tested that.

时光倒影 2024-12-19 07:00:24

这是另一个自制的休息模拟工具:https://github.com/mkotsur/restito

Here is another homegrown rest-mocking tool: https://github.com/mkotsur/restito.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文