|
| 1 | +## Summary |
| 2 | +This enhancement proposal recommends adding an ingress feature to facilitate HTTP handling to the Ray Serve package in Java. |
| 3 | + |
| 4 | +### General Motivation |
| 5 | +We want to call various methods within a Ray Serve Java deployment through HTTP proxy, in a way that's similar to the [FastAPI ingress](https://docs.ray.io/en/latest/serve/http-guide.html#fastapi-http-deployments) in Python version. |
| 6 | + |
| 7 | +### Should this change be within `ray` or outside? |
| 8 | +The main `ray` project. Changes are made to the Ray Serve module in the Java codebase. |
| 9 | + |
| 10 | +## Stewardship |
| 11 | + |
| 12 | +### Required Reviewers |
| 13 | +@simon-mo @sihanwang41 |
| 14 | + |
| 15 | +### Shepherd of the Proposal (should be a senior committer) |
| 16 | +@simon-mo |
| 17 | + |
| 18 | +## Design and Architecture |
| 19 | + |
| 20 | +### User Workflow |
| 21 | + |
| 22 | +1. Define the model with [JAX-RS API](https://docs.oracle.com/javaee/6/tutorial/doc/gilik.html) |
| 23 | + |
| 24 | +```java |
| 25 | +@Path("user") |
| 26 | +public class UserRestService { |
| 27 | + @GET |
| 28 | + @Path("helloWorld") |
| 29 | + @Consumes({"application/json"}) |
| 30 | + @Produces({"application/json"}) |
| 31 | + public String helloWorld(String name) { |
| 32 | + return "hello world, " + name; |
| 33 | + } |
| 34 | + |
| 35 | + @GET |
| 36 | + @Path("paramPathTest/{name}") |
| 37 | + @Consumes({"application/json"}) |
| 38 | + @Produces({"application/json"}) |
| 39 | + public String paramPathTest(@PathParam("name")String name) { |
| 40 | + return "paramPathTest, " + name; |
| 41 | + } |
| 42 | +} |
| 43 | +``` |
| 44 | +2. Create deployment |
| 45 | +```java |
| 46 | +Deployment deployment = |
| 47 | + Serve.deployment() |
| 48 | + .setName("deploymentName") |
| 49 | + .setDeploymentDef(UserRestService.class.getName()) |
| 50 | + .ingress("jax-rs") |
| 51 | + .setNumReplicas(1) |
| 52 | + .create(); |
| 53 | +deployment.deploy(); |
| 54 | +``` |
| 55 | + |
| 56 | +3. Calling deployments via HTTP |
| 57 | + |
| 58 | +The URI is determined by the deployment name and JAX-RS @PATH annotations. |
| 59 | +```java |
| 60 | +curl http://127.0.0.1:8000/deploymentName/user/helloWorld?name=test |
| 61 | +curl http://127.0.0.1:8000/deploymentName/user/paramPathTest/test |
| 62 | +``` |
| 63 | + |
| 64 | +### HTTP Ingress Implementation |
| 65 | +#### Ingress API Annotation: JAX-RS |
| 66 | + |
| 67 | +In Java application development, restful API is generally implemented through two sets of annotations, spring web or JAX-RS. |
| 68 | + |
| 69 | +JAX-RS is a specification for implementing REST web services in Java, currently defined by the JSR-370. JAX-RS is just an API specification and has been implemented by many components: jersey, resteasy, etc. |
| 70 | + |
| 71 | +Spring-web contains a lot of features that we don't need to use: IOC (inversion of control), DI (dependency injection), etc. The parsing of spring-web annotations depends on the spring framework. |
| 72 | + |
| 73 | +In order not to import too many unnecessary dependencies to the user's `Callable`. We choose JAX-RS to support HTTP ingress. |
| 74 | + |
| 75 | +For more information about JAX-RS and spring web, please click the following links. |
| 76 | + |
| 77 | +JAX-RS: https://projects.eclipse.org/projects/ee4j.rest |
| 78 | + |
| 79 | +Spring-web: https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-requestmapping |
| 80 | + |
| 81 | +#### Annotation parser: Jersey |
| 82 | +Jersey RESTful Web Services 2.x framework is open source, production quality, a framework for developing RESTful Web Services in Java that provides support for JAX-RS APIs and serves as a JAX-RS (JSR 311 & JSR 339 & JSR 370) Reference Implementation. |
| 83 | + |
| 84 | +Jersey framework is more than the JAX-RS Reference Implementation. Jersey provides its own API that extends the JAX-RS toolkit with additional features and utilities to further simplify RESTful service and client development. Jersey also exposes numerous extension SPIs so that developers may extend Jersey to best suit their needs. |
| 85 | + |
| 86 | +The most important thing is that the Jersey does not depend on a servlet container to run. And many other open-source frameworks need to integrate servlet containers. |
| 87 | + |
| 88 | +For more information about Jersey, please click this link: https://eclipse-ee4j.github.io/jersey/ |
| 89 | + |
| 90 | +##### Maven dependency |
| 91 | + |
| 92 | +```xml |
| 93 | +<dependency> |
| 94 | + <groupId>org.glassfish.jersey.core</groupId> |
| 95 | + <artifactId>jersey-server</artifactId> |
| 96 | + <version>2.30.1</version> |
| 97 | +</dependency> |
| 98 | +<dependency> |
| 99 | + <groupId>org.glassfish.jersey.inject</groupId> |
| 100 | + <artifactId>jersey-hk2</artifactId> |
| 101 | + <version>2.30.1</version> |
| 102 | +</dependency> |
| 103 | +``` |
| 104 | +##### Jersey Callable Integration |
| 105 | +In order to integrate Jersey in the the current `ray.serve` package, we will need to take the following step: |
| 106 | + |
| 107 | +- convert `RequestWrapper` to jersey `ContainerRequest` |
| 108 | +- call `ApplicationHandler.apply` with `ContainerRequest` |
| 109 | +- return `ContainerResponse.getEntity` to the HTTP proxy |
| 110 | + |
| 111 | +```java |
| 112 | +public class JaxrsIngressCallable { |
| 113 | + private ApplicationHandler app; |
| 114 | + |
| 115 | + public JaxrsIngressCallable(Class clazz) { |
| 116 | + ResourceConfig resourceConfig = new ResourceConfig(clazz); |
| 117 | + this.app = new ApplicationHandler(resourceConfig); |
| 118 | + } |
| 119 | + |
| 120 | + public Object call(RequestWrapper httpProxyRequest) { |
| 121 | + ContainerRequest jerseyRequest = convertRequestWrap2ContainerRequest(httpProxyRequest) |
| 122 | + ContainerResponse response = app.apply(jerseyRequest).get(); |
| 123 | + Object rspBody = response.getEntity(); |
| 124 | + return rspBody; |
| 125 | + } |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +### Extension of `callable` |
| 130 | +Normally, we use the class instance as a `ray.serve.Callable`. But in HTTP ingress, the `ray.serve.Callable` we use is wrapped with the Jersey application handler. We have two different implementations of `ray.serve.Callable`. |
| 131 | + |
| 132 | +In python, when we add the `@ingress` annotation to an object, a new object will be generated, that is, a new `ray.serve.Callable` instance will be generated. |
| 133 | + |
| 134 | +In java, we add annotations to the class, but it can not enhance the features of `ray.serve.Callable`. So we need a mechanism to generate different `ray.serve.Callable` instances according to different ingress types. |
| 135 | + |
| 136 | +Here we use java SPI to implement this feature. We add a `ServeCallableProvider` SPI. |
| 137 | + |
| 138 | +```java |
| 139 | +public interface ServeCallableProvider { |
| 140 | + /** |
| 141 | + * Get Callable type |
| 142 | + * @return Callable type |
| 143 | + */ |
| 144 | + String type(); |
| 145 | + |
| 146 | + /** |
| 147 | + * Generate a Callable instance |
| 148 | + * @param deploymentWrapper deployment info and config |
| 149 | + * @return Callable instance |
| 150 | + */ |
| 151 | + Object buildCallable(DeploymentWrapper deploymentWrapper); |
| 152 | + |
| 153 | + /** |
| 154 | + * Get the signature of callable |
| 155 | + * @param deploymentWrapper deployment info and config |
| 156 | + * @param callable Callable instance |
| 157 | + * @return |
| 158 | + */ |
| 159 | + Map<String, Pair<Method, Object>> getSignatures(DeploymentWrapper deploymentWrapper, Object callable); |
| 160 | +} |
| 161 | +``` |
| 162 | + |
| 163 | +The current version of `Callable` is implemented as the default interface implementation. An additional implementation of the `ServeCallableProvider` is added for each additional ingress type. |
| 164 | + |
| 165 | +### Method Signature Cache |
| 166 | +Typically, our `Callable` are user-provided class instances. In JAX-RS, the `Callable` is the jersey application handler when the replica is called using an HTTP proxy. When using the serve handle to call the replica, we cannot call any methods in the class. We are not compatible with the serve handle. |
| 167 | + |
| 168 | +On the other hand, every time we call replica, we need to use reflection to get the method that needs to be executed. This will reduce the performance and throughput of the replica |
| 169 | + |
| 170 | +In order to solve the above problems, We need to hold the cache of method signatures. |
| 171 | + |
| 172 | + |
| 173 | + |
| 174 | +Ray core uses the signature to decide which method to call. we want to be consistent with it. |
| 175 | + |
| 176 | +When init java replica, we will parse signatures from the `Callable` class. Generate the following map. the key is the method signature, value is the pair of the method instance and the `Callable` instance. |
| 177 | + |
| 178 | +```java |
| 179 | +Map<String, Pair<Method, Object>> signatures; |
| 180 | +``` |
| 181 | + |
| 182 | +If the user configures JAX-rs ingress, we will add one data to the signature cache. The key is always set to `__call__`, the left of the pair in the value is a `Method` instance of the `JaxrsIngressCallable.call` and the right of the pair is an instance of `JaxrsIngressCallable`. |
| 183 | + |
| 184 | +For requests from HTTP proxy, the method signature will be fixed to `__call__`. The request from the serve handle will set the method signature on the client side and hit the signature cache on the server side. |
| 185 | + |
| 186 | +## Compatibility, Deprecation, and Migration Plan |
| 187 | +New features are incremental and do not affect any existing features. And we use SPI to make the modification of the java HTTP ingress meet the open-closed principle. |
| 188 | + |
| 189 | +## Test Plan and Acceptance Criteria |
| 190 | +- Unit and integration test for core components |
| 191 | +- Benchmarks on java HTTP ingress performance |
| 192 | + |
| 193 | +## (Optional) Follow-on Work |
| 194 | +- `callable` support SPI |
| 195 | +- method signature cache |
| 196 | +- http ingress with jersey application handler |
| 197 | +- benchmark test |
0 commit comments