如何从包含URL的文件自动生成REST端点?

huangapple go评论81阅读模式
英文:

How to automatically generate REST endpoints from a file containing urls?

问题

我有一个包含必须在 REST API 上公开的 URL 的文件。所有的 URL 都代表不同的资源,它们必须分别进行文档化。该文件包含成百上千个类似以下的 URL:

/p1
/p1/p2
/p1/p2/p3
/t1
/t1/t2
/t1/t2/t3

我希望能够自动生成 REST 端点,以便我能够调用:

对 https://host/p1/ 执行 GET 请求
对 https://host/p1/p2 执行 GET 请求
对 https://host/p1/p3 执行 GET 请求

... 以此类推 ...

处理这些请求的逻辑对于每个路径都是类似的,因此 /p1、/p1/p2、/t1 等可以由一个接收整个路径的单个函数来处理。为了处理请求,我有一个像这样的函数:

function handleRequest(url) {
    // 该函数应该针对这些端点上的每个 GET 请求进行调用
    // 在这里执行业务逻辑
}

数据存储在树状数据结构中,因此在树中获取路径并返回路径所指节点下面的数据是有意义的。然而,所有这些路径都是独立的资源。

我正在用 Java 编写代码,但语言现在并不重要。我想要在 Spring 中生成类似这样的 REST 端点:

@RequestMapping(
    path = "/t1",
    method = RequestMethod.GET,
    produces = MediaType.APPLICATION_JSON_VALUE,
    consumes = MediaType.APPLICATION_JSON_VALUE,
    headers = "Accept=application/json"
)
public ResponseEntity getT1() {
   // 在这里处理请求
}

然而,由于必须通过 API 公开的资源数量非常多,对于上述的每个资源,按照同样的逻辑编写类似上面的函数会非常困难。

我找到的一个解决方案是使用 PathPatterns。这将涉及到一个带有像 "/commonPath/**" 这样的 url 的 RequestMapping。这种解决方案的缺点是我不能通过 Swagger 自动为 API 生成文档,因为只有一个入口点。

如何能够自动生成所有这些端点呢?

编辑

文档基本上归结为以自动方式告知用户可以调用哪些可用的端点。我不想手动编写/维护这些内容,因为 URL 的数量非常多。使用 Swagger,非常简单。只需为端点添加注解,就会为 API 的客户端自动生成用户界面。

英文:

I have a file that contains URLs that must be exposed on a REST API. All URLs represent distinct resources and they must be documented separately. The file contains hundreds of URLs like:

/p1
/p1/p2
/p1/p2/p3
/t1
/t1/t2
/t1/t2/t3

I want to automatically / programmatically generate REST endpoints so that I will be able to call:

GET on https://host/p1/
GET on https://host/p1/p2
GET on https://host/p1/p3

... and so on ...

The logic behind handling the requests is similar for every path, so /p1, /p1/p2, /t1, and so on can be handled by a single function that receives the entire path. In order to handle the request I have a function like this:

function handleRequest(url) {
    // this function should be called for every GET request on any of those endpoints
    // perform the business logic here 
}

Data is stored in a tree data structure, so it makes sense to get a path in the tree and return the data underneath the node the path points to. However, all of these paths are separate resources.

I am writing the code in Java, but the language is not important for now. I would generate a REST endpoint in Spring like this:

    @RequestMapping(
        path = "/t1",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE,
        consumes = MediaType.APPLICATION_JSON_VALUE,
        headers = "Accept=application/json"
    )
    public ResponseEntity getT1() {
       // handle request here
    }

However, since the number of resources that must be exposed through the API is very high, it is extremely hard to write a function like the one above for hundreds or thousands of times when the logic that handles the request is the same.

One solution that I found is to use PathPatterns. This would entail having a RequestMapping with a url like "/commonPath/**". The downside of this solution is that I cannot automatically generate documentation via Swagger for the API since there is a single entry point.

How can I automatically generate all these endpoints?

Edit

Documentation basically boils down to having an automatic way of telling what are the available endpoints the user can call. I do not want to write/maintain this manually since the number of URLs is very high. With Swagger, it is very simple. Just annotate the endpoints and a user interface is automatically generated for the clients of the API.

答案1

得分: 3

我希望了解更多关于您的情况的详细信息,但根据目前提供的信息,以下是可能的选项。

解决方案1: 如果您的输入文件是静态的,并且您需要基于静态输入文件公开API,则此解决方案可能有效。只需编写一个程序,根据文件生成控制器类。

@RestController
public class MyMultiPathApiController {

    @Autowired
    private RestApiHandlerService restApiHandlerService;

    @RequestMapping(
        value = { "/p1", "/p1/p2", "/p1/p2/p3" }, 
        method = GET)
    public ResponseEntity<?> myMultiRestApi() {
        return restApiHandlerService.handleApiLogic();
    }
}

另外,RequestMapping还接受正则表达式。根据您提供的示例,您也可以构建一个正则表达式。

解决方案2(推荐的解决方案):

如果您的文件预计会更改,并且您想要动态地公开API,那么您需要调整Spring框架的 RequestMappingHandlerMapping 类。扩展 RequestMappingHandlerMapping 类并编写您想要的逻辑。您可以在应用程序启动时读取文件并进行缓存。您可以参考一些关于编写自定义 RequestMappingHandlerMapping 类的示例这里

希望这些信息对您有所帮助。如果您有任何后续问题,请随时告诉我。

英文:

I wish to know more details about your scenario but here are the possible options based on the given information at this point in time.

Solution 1: If your input file is static and you need to expose the APIs based on the static input file then this solution may work well. Just write a program to generate the controller class based on the file.

@RestController
public class MyMultiPathApiController {

	@AutoWired
	private RestApiHandlerService restApiHandlerService

	@RequestMapping(
  value = { &quot;/p1&quot;, &quot;/p1/p2&quot;, &quot;/p1/p2/p3&quot; }, 
  method = GET)
	public ResponseEntity&lt;?&gt; myMultiRestApi() {
		return restApiHandlerService.handleApiLogic();
	}
}

Alternatively request mapping accepts regular expressions as well. Based on the examples you have given you can come up with a regular expression as well.

Solution 2 (Recommended Solution):

If your file is expected to change and you want to expose API dynamically then you need to tweak the spring framework's RequestMappingHandlerMapping class. Extend the class RequestMappingHandlerMapping and write the logic which you want to write. You can read the file on the application start and cache it. You can refer to some examples on writing a custom RequestMappingHandlerMapping class here.

I trust this information is helpful. Please let me know if you have any follow up questions.

答案2

得分: 0

我会使用Java注解处理器以及类似mustache的模板框架或类似javapoet的Java代码生成器来生成Spring控制器。

英文:

I would generate the spring controllers using a java annotation processor coupled with either a templating framework like mustache or a java code emitter like javapoet

答案3

得分: 0

感谢 @Lokesh 提供的答案。不幸的是,我无法完全理解 RequestMappingHandlerMapping 并构建一个完整的工作示例。这个答案与 @Andrew S 在评论中的建议类似。

我创建了一个类,通过两个模板文件生成所有路径的代码,并将生成的 Java 类保存在项目中。

我创建了一个类模板,它只是一个文本文件:

package a.b.c.d;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@Api(description = "资源控制器")
@RestController
@RequestMapping(path = "REQUEST_MAPPING_BASE_PATH")
public class Resources {
  REQUEST_METHODS
}

然后,我创建了一个方法模板,也是一个文本文件:

@RequestMapping(
  path = "REQUEST_MAPPING_PATH",
  method = RequestMethod.REQUEST_HTTP_METHOD,
  produces = MediaType.ALL_VALUE,
  consumes = MediaType.ALL_VALUE,
  headers = "Accept=application/json"
)
@ApiOperation(
  value = "REQUEST_METHOD_DESCRIPTION",
  response = ResponseEntity.class
)
public ResponseEntity REQUEST_METHOD_NAME(HttpServletRequest request) {
  String path = request.getRequestURI();
  return new GraphResourceHandler(path).execute();
}

GraphResourceHandler 是执行每个路径逻辑的类:

public class GraphResourceHandler {
	private final String path;

	public GraphResourceHandler(String path) {
		this.path = path;
	}

	public ResponseEntity execute() {
		//TODO 实现逻辑
	}

	public String getPath() {
		return path;
	}
}

然后通过生成所有资源的 .java 文件是微不足道的。我只会提供伪代码:

class GenerateResources {

	public static void main(String[] args) {
	    readFileContainingPaths();
        
        // 保存方法定义的字符串
        String methods = "";

        for (resource in paths) {
            // 简单地对方法模板中的变量进行字符串替换
            // 例如,将 REQUEST_METHOD_NAME 替换为自定义名称
            methods += generateMethodFromResource(resource);
        }
        
        // 将 REQUEST_METHODS 变量替换为 methods 变量的内容
        addResourceMethodsToClassTemplate()
	    writeResultingStringClassToJavaFile()
     }
}

编译项目,完成。当然,每当您向文件中添加新值时,必须重新运行这个类,但我认为这可以进一步自动化。优点是我不必编写成千上万行的代码,而且 Swagger 知道如何自动生成资源。

英文:

Thanks @Lokesh for the answer. Unfortunately, I was not able to fully understand RequestMappingHandlerMapping and build a full working example. The answer is similar to @Andrew S suggestion in the comment.

I built a class that generates code for all the paths via 2 template files and saves the resulting java class in the project.

I have created a class template which is simply a text file:

package a.b.c.d;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@Api (description = &quot;Resource controller&quot;)
@RestController
@RequestMapping (path = &quot;REQUEST_MAPPING_BASE_PATH&quot;)
public class Resources {
  REQUEST_METHODS
}

I have then created a method template which is also a text file:

@RequestMapping (
  path = &quot;REQUEST_MAPPING_PATH&quot;,
  method = RequestMethod.REQUEST_HTTP_METHOD,
  produces = MediaType.ALL_VALUE,
  consumes = MediaType.ALL_VALUE,
  headers = &quot;Accept=application/json&quot;
)
@ApiOperation (
  value = &quot;REQUEST_METHOD_DESCRIPTION&quot;,
  response = ResponseEntity.class
)
public ResponseEntity REQUEST_METHOD_NAME (HttpServletRequest request) {
  String path = request.getRequestURI();
  return new GraphResourceHandler(path).execute();
}

GraphResourceHandler is the class that executes the logic for each path:

public class GraphResourceHandler {
	private final String path;

	public GraphResourceHandler(String path) {
		this.path = path;
	}

	public ResponseEntity execute() {
		//TODO implement logic
	}

	public String getPath() {
		return path;
	}
}

Generating the .java file with all resource is then trivial. I will only provide pseudocode:

class GenerateResources {

	public static void main(String[] args) {
	    readFileContainingPaths();
        
        // holds method definitions as string
        String methods = &quot;&quot;;

        foreach(resource in paths) {
            //simply perform string replacement for variables in method template
            // eg. replace REQUEST_METHOD_NAME with a custom name
            methods += generateMethodFromResource(resource);
        }
        
        // replace REQUEST_METHODS variable to the contents of &#39;methods&#39; variable
        addResourceMethodsToClassTemplate()
	    writeResultingStringClassToJavaFile()
     }
}

Compile the project and there you go. Of course, whenever you add a new value to the file, this class must be rerun, but I think it can be further automated. The advantage is that I don't have to write thousands of lines of code and that Swagger knows how to generate the resources automatically.

huangapple
  • 本文由 发表于 2020年10月20日 20:24:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/64445112.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定