Skip to content

Commit

Permalink
feat(内核): 支持 @Header 注解
Browse files Browse the repository at this point in the history
  • Loading branch information
qianmoQ committed Jan 17, 2025
1 parent 4b4bfd6 commit b96c591
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 20 deletions.
8 changes: 6 additions & 2 deletions docs/content/usage/get.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ List<Post> getPosts();

该示例中的 `getPosts` 是一个 HTTP GET 请求,请求路径是 `/posts`

### 请求参数 (可选)
### `@RequestParam`

---

!!! info "提示"

Expand All @@ -49,7 +51,9 @@ List<Post> getPosts(
/posts?page=1&size=10
```

### 路径参数 (可选)
### `@PathVariable`

---

!!! info "提示"

Expand Down
41 changes: 41 additions & 0 deletions docs/content/usage/header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
title: Header
---

LightCall 提供了 HTTP Header 的支持。

!!! danger "注意"

LightCall 支持多种 Header 的传递方式,包括 `@Header` 和 `@Headers` 两种。

!!!

### 用法

```java
@Get("/posts")
List<Post> getPosts(
@Header("Authorization") String authorization
);
```

该示例中的 `getPosts` 是一个 HTTP GET 请求,请求路径是 `/posts`,请求头是 `Authorization`

### `@Header`

---

!!! info "注意"

`@Header` 注解只能在参数上使用。

!!!

```java
@Get("/posts")
List<Post> getPosts(
@Header("Authorization") String authorization
)
```

该示例中的 `getPosts` 是一个 HTTP GET 请求,请求路径是 `/posts`,请求头是 `Authorization`。系统会自动将 `authorization` 参数的值传递到请求头中。
1 change: 1 addition & 0 deletions docs/pageforge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ site:

nav:
- 文档:
- /usage/header
- /usage/get
25 changes: 25 additions & 0 deletions src/main/java/org/devlive/lightcall/RequestContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.devlive.lightcall;

import lombok.Getter;
import okhttp3.HttpUrl;
import okhttp3.Request;

@Getter
public class RequestContext
{
private final HttpUrl.Builder urlBuilder;
private final Request.Builder requestBuilder;

private RequestContext(HttpUrl.Builder urlBuilder, Request.Builder requestBuilder)
{
this.urlBuilder = urlBuilder;
this.requestBuilder = requestBuilder;
}

public static RequestContext create(String baseUrl)
{
HttpUrl.Builder urlBuilder = HttpUrl.parse(baseUrl).newBuilder();
Request.Builder requestBuilder = new Request.Builder();
return new RequestContext(urlBuilder, requestBuilder);
}
}
17 changes: 17 additions & 0 deletions src/main/java/org/devlive/lightcall/annotation/Header.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.devlive.lightcall.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Header
{
String value();

boolean required() default true;
}
57 changes: 57 additions & 0 deletions src/main/java/org/devlive/lightcall/handler/HeaderHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.devlive.lightcall.handler;

import lombok.extern.slf4j.Slf4j;
import okhttp3.Request;
import org.devlive.lightcall.annotation.Header;

import java.lang.reflect.Parameter;

@Slf4j
public class HeaderHandler
implements ParameterHandler
{
private final Request.Builder requestBuilder;

private HeaderHandler(Request.Builder requestBuilder)
{
this.requestBuilder = requestBuilder;
}

public static HeaderHandler create(Request.Builder requestBuilder)
{
return new HeaderHandler(requestBuilder);
}

@Override
public boolean canHandle(Parameter parameter)
{
return parameter.isAnnotationPresent(Header.class);
}

@Override
public String handle(Parameter parameter, Object arg, String path)
{
// 处理参数级 @Header
if (parameter != null && parameter.isAnnotationPresent(Header.class)) {
handleParameterHeader(parameter, arg);
}
return path;
}

private void handleParameterHeader(Parameter parameter, Object arg)
{
Header annotation = parameter.getAnnotation(Header.class);
String headerName = annotation.value();

if (arg == null) {
if (annotation.required()) {
throw new IllegalArgumentException(
String.format("Header '%s' is required but was null", headerName));
}
return;
}

log.debug("Adding parameter header - {}: {}", headerName, arg);
requestBuilder.addHeader(headerName, String.valueOf(arg));
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package org.devlive.lightcall.handler;

import okhttp3.HttpUrl;
import org.devlive.lightcall.RequestContext;

import java.util.ArrayList;
import java.util.List;

public class ParameterHandlerFactory
{
public static List<ParameterHandler> createHandlers(HttpUrl.Builder urlBuilder)
public static List<ParameterHandler> createHandlers(RequestContext context)
{
List<ParameterHandler> handlers = new ArrayList<>();
handlers.add(RequestParamHandler.create(urlBuilder));
handlers.add(RequestParamHandler.create(context.getUrlBuilder()));
handlers.add(PathVariableHandler.create());
handlers.add(HeaderHandler.create(context.getRequestBuilder()));
return handlers;
}
}
50 changes: 35 additions & 15 deletions src/main/java/org/devlive/lightcall/proxy/LightCallProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.devlive.lightcall.RequestContext;
import org.devlive.lightcall.RequestException;
import org.devlive.lightcall.annotation.Get;
import org.devlive.lightcall.config.LightCallConfig;
Expand Down Expand Up @@ -40,7 +41,11 @@ public LightCallProxy(LightCallConfig config)
}

@Override
public Object invoke(Object proxy, Method method, Object[] args)
public Object invoke(
Object proxy,
Method method,
Object[] args
)
throws Throwable
{
log.debug("Invoking method: {}.{}({})",
Expand All @@ -56,34 +61,41 @@ public Object invoke(Object proxy, Method method, Object[] args)
Get getAnnotation = method.getAnnotation(Get.class);
if (getAnnotation != null) {
log.debug("Processing @Get annotation with value: {}", getAnnotation.value());
HttpUrl url = buildUrl(getAnnotation.value(), method, args);
return executeGet(url, method.getReturnType());

// 创建请求上下文
RequestContext context = RequestContext.create(config.getBaseUrl());

// 构建 URL 和处理 headers
HttpUrl url = buildUrl(getAnnotation.value(), method, args, context);

return executeGet(url, method.getReturnType(), context);
}

throw new UnsupportedOperationException(
String.format("Method %s is not annotated with @Get", method.getName()));
}

private HttpUrl buildUrl(String path, Method method, Object[] args)
private HttpUrl buildUrl(
String path,
Method method,
Object[] args,
RequestContext context
)
{
log.debug("Building URL for path: {} with args: {}", path, Arrays.toString(args));

// 创建基础 URL 构建器
HttpUrl.Builder urlBuilder = HttpUrl.parse(config.getBaseUrl())
.newBuilder();

// 获取参数处理器
List<ParameterHandler> handlers = ParameterHandlerFactory.createHandlers(urlBuilder);
List<ParameterHandler> handlers = ParameterHandlerFactory.createHandlers(context);

// 处理参数
Parameter[] parameters = method.getParameters();
String processedPath = path;

// 处理参数级注解
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
Object arg = args[i];

// 找到合适的处理器处理参数
for (ParameterHandler handler : handlers) {
if (handler.canHandle(parameter)) {
processedPath = handler.handle(parameter, arg, processedPath);
Expand All @@ -96,22 +108,27 @@ private HttpUrl buildUrl(String path, Method method, Object[] args)
if (!processedPath.startsWith("/")) {
processedPath = "/" + processedPath;
}
urlBuilder.addPathSegments(processedPath.substring(1));
context.getUrlBuilder().addPathSegments(processedPath.substring(1));

HttpUrl url = urlBuilder.build();
HttpUrl url = context.getUrlBuilder().build();
log.debug("Built URL: {}", url);
return url;
}

private <T> T executeGet(HttpUrl url, Class<T> returnType)
private <T> T executeGet(
HttpUrl url,
Class<T> returnType,
RequestContext context
)
throws Exception
{
log.info("Executing GET request - URL: {}, Expected return type: {}", url, returnType);

Request request = new Request.Builder()
Request request = context.getRequestBuilder()
.url(url)
.get()
.build();
log.info("Executing request - URL: {}, Headers: {}", url, request.headers());

long startTime = System.currentTimeMillis();
try (Response response = client.newCall(request).execute()) {
Expand All @@ -133,7 +150,10 @@ private <T> T executeGet(HttpUrl url, Class<T> returnType)
}
}

private String formatMethodArgs(Method method, Object[] args)
private String formatMethodArgs(
Method method,
Object[] args
)
{
if (args == null || args.length == 0) {
return "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ public void testGetPostParamAndPath()
{
Assertions.assertNotNull(service.getPostParamAndPath(1L, "title"));
}

@Test
public void testGetPostPathAndHeader()
{
Assertions.assertNotNull(service.getPostPathAndHeader(1L, "apiKey"));
}
}
7 changes: 7 additions & 0 deletions src/test/java/org/devlive/lightcall/example/PostService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.devlive.lightcall.example;

import org.devlive.lightcall.annotation.Get;
import org.devlive.lightcall.annotation.Header;
import org.devlive.lightcall.annotation.PathVariable;
import org.devlive.lightcall.annotation.RequestParam;

Expand All @@ -25,4 +26,10 @@ Post getPostParamAndPath(
@PathVariable("id") Long id,
@RequestParam("title") String title
);

@Get("/posts/{id}")
Post getPostPathAndHeader(
@PathVariable("id") Long id,
@Header("x-api-key") String apiKey
);
}

0 comments on commit b96c591

Please sign in to comment.