From 647ca585284030b3cef72da77b6d4430a1b32bc9 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Fri, 18 Oct 2024 00:15:33 -0700 Subject: [PATCH] feat: support set multiple cookies in http response --- .../functions/worker/binding/HttpCookie.java | 101 +++++++++++++ .../worker/binding/RpcHttpDataTarget.java | 139 +++++++++++++----- 2 files changed, 202 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/microsoft/azure/functions/worker/binding/HttpCookie.java diff --git a/src/main/java/com/microsoft/azure/functions/worker/binding/HttpCookie.java b/src/main/java/com/microsoft/azure/functions/worker/binding/HttpCookie.java new file mode 100644 index 00000000..c23de979 --- /dev/null +++ b/src/main/java/com/microsoft/azure/functions/worker/binding/HttpCookie.java @@ -0,0 +1,101 @@ +package com.microsoft.azure.functions.worker.binding; +import com.microsoft.azure.functions.rpc.messages.RpcHttpCookie; +public class HttpCookie { + private String name; + private String value; + private String domain; + private String path; + private Boolean secure; + private Boolean httpOnly; + private String expires; // In ISO 8601 format + private Double maxAge; + private RpcHttpCookie.SameSite sameSite; + + // Constructor with required fields + public HttpCookie(String name, String value) { + this.name = name; + this.value = value; + } + + // Getters and setters with chainable methods + public String getName() { + return name; + } + + public HttpCookie setName(String name) { + this.name = name; + return this; + } + + public String getValue() { + return value; + } + + public HttpCookie setValue(String value) { + this.value = value; + return this; + } + + public String getDomain() { + return domain; + } + + public HttpCookie setDomain(String domain) { + this.domain = domain; + return this; + } + + public String getPath() { + return path; + } + + public HttpCookie setPath(String path) { + this.path = path; + return this; + } + + public Boolean getSecure() { + return secure; + } + + public HttpCookie setSecure(Boolean secure) { + this.secure = secure; + return this; + } + + public Boolean getHttpOnly() { + return httpOnly; + } + + public HttpCookie setHttpOnly(Boolean httpOnly) { + this.httpOnly = httpOnly; + return this; + } + + public String getExpires() { + return expires; + } + + public HttpCookie setExpires(String expires) { + this.expires = expires; + return this; + } + + public Double getMaxAge() { + return maxAge; + } + + public HttpCookie setMaxAge(Double maxAge) { + this.maxAge = maxAge; + return this; + } + + public com.microsoft.azure.functions.rpc.messages.RpcHttpCookie.SameSite getSameSite() { + return sameSite; + } + + public HttpCookie setSameSite(RpcHttpCookie.SameSite sameSite) { + this.sameSite = sameSite; + return this; + } +} \ No newline at end of file diff --git a/src/main/java/com/microsoft/azure/functions/worker/binding/RpcHttpDataTarget.java b/src/main/java/com/microsoft/azure/functions/worker/binding/RpcHttpDataTarget.java index 16b45f19..5e0b36f7 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/binding/RpcHttpDataTarget.java +++ b/src/main/java/com/microsoft/azure/functions/worker/binding/RpcHttpDataTarget.java @@ -1,42 +1,70 @@ package com.microsoft.azure.functions.worker.binding; +import java.time.Instant; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import com.google.protobuf.Timestamp; import com.microsoft.azure.functions.HttpResponseMessage; import com.microsoft.azure.functions.HttpStatus; import com.microsoft.azure.functions.HttpStatusType; +import com.microsoft.azure.functions.rpc.messages.NullableTypes; import com.microsoft.azure.functions.rpc.messages.RpcHttp; +import com.microsoft.azure.functions.rpc.messages.RpcHttpCookie; import com.microsoft.azure.functions.rpc.messages.TypedData; final class RpcHttpDataTarget extends DataTarget implements HttpResponseMessage, HttpResponseMessage.Builder { RpcHttpDataTarget() { super(HTTP_TARGET_OPERATIONS); this.headers = new HashMap<>(); + this.cookies = new ArrayList<>(); this.httpStatus = HttpStatus.OK; this.httpStatusCode = HttpStatus.OK.value(); super.setValue(this); } @Override - public HttpStatusType getStatus() { return httpStatus; } - @Override - public int getStatusCode() { return httpStatusCode; } + public HttpStatusType getStatus() { + return httpStatus; + } + + @Override + public int getStatusCode() { + return httpStatusCode; + } + @Override - public String getHeader(String key) { return this.headers.get(key); } + public String getHeader(String key) { + return this.headers.get(key); + } + @Override - public Object getBody() { return this.body; } + public Object getBody() { + return this.body; + } private int httpStatusCode; private HttpStatusType httpStatus; private Object body; private Map headers; + private List cookies; public static TypedData.Builder toRpcHttpData(RpcHttpDataTarget response) throws Exception { TypedData.Builder dataBuilder = TypedData.newBuilder(); if (response != null) { - RpcHttp.Builder httpBuilder = RpcHttp.newBuilder().setStatusCode(Integer.toString(response.getStatusCode())); + RpcHttp.Builder httpBuilder = RpcHttp.newBuilder() + .setStatusCode(Integer.toString(response.getStatusCode())); + + // Add headers response.headers.forEach(httpBuilder::putHeaders); + + // Add cookies + if (response.cookies != null) { + httpBuilder.addAllCookies(response.cookies); + } + RpcUnspecifiedDataTarget bodyTarget = new RpcUnspecifiedDataTarget(); bodyTarget.setValue(response.getBody()); bodyTarget.computeFromValue().ifPresent(httpBuilder::setBody); @@ -51,46 +79,81 @@ public static TypedData.Builder toRpcHttpData(RpcHttpDataTarget response) throws HTTP_TARGET_OPERATIONS.addTargetOperation(RpcHttpDataTarget.class, v -> toRpcHttpData((RpcHttpDataTarget) v)); } - - public Builder status(HttpStatus status) { - this.httpStatusCode = status.value(); - this.httpStatus = status; - return this; - } - - - @Override - public Builder status(HttpStatusType httpStatusType) { - this.httpStatusCode = httpStatusType.value(); - this.httpStatus = httpStatusType; - return this; - } - - + @Override + public Builder status(HttpStatusType httpStatusType) { + this.httpStatusCode = httpStatusType.value(); + this.httpStatus = httpStatusType; + return this; + } + public Builder status(int httpStatusCode) { if (httpStatusCode < 100 || httpStatusCode > 599) { throw new IllegalArgumentException("Invalid HTTP Status code class. Valid classes are in the range of 1xx, 2xx, 3xx, 4xx and 5xx."); } this.httpStatusCode = httpStatusCode; - this.httpStatus = HttpStatusType.custom(httpStatusCode); + this.httpStatus = HttpStatusType.custom(httpStatusCode); return this; } - - @Override - public Builder header(String key, String value) { + @Override + public Builder header(String key, String value) { this.headers.put(key, value); - return this; - } + return this; + } + + public Builder cookie(HttpCookie cookie) { + this.cookies.add(convertToRpcHttpCookie(cookie)); + return this; + } - @Override - public Builder body(Object body) { + @Override + public Builder body(Object body) { this.body = body; - return this; - } - - @Override - public HttpResponseMessage build() { - return this; - } -} \ No newline at end of file + return this; + } + + @Override + public HttpResponseMessage build() { + return this; + } + + private RpcHttpCookie convertToRpcHttpCookie(HttpCookie cookie) { + RpcHttpCookie.Builder builder = RpcHttpCookie.newBuilder() + .setName(cookie.getName()) + .setValue(cookie.getValue()); + + if (cookie.getDomain() != null) { + builder.setDomain(NullableTypes.NullableString.newBuilder().setValue(cookie.getDomain()).build()); + } + if (cookie.getPath() != null) { + builder.setPath(NullableTypes.NullableString.newBuilder().setValue(cookie.getPath()).build()); + } + if (cookie.getExpires() != null) { + try { + // Parse the expires string into an Instant + Instant instant = Instant.parse(cookie.getExpires()); + // Build the Timestamp from the Instant + Timestamp expiresTimestamp = Timestamp.newBuilder() + .setSeconds(instant.getEpochSecond()) + .setNanos(instant.getNano()) + .build(); + builder.setExpires(NullableTypes.NullableTimestamp.newBuilder().setValue(expiresTimestamp).build()); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid expires format in cookie", e); + } + } + if (cookie.getMaxAge() != null) { + builder.setMaxAge(NullableTypes.NullableDouble.newBuilder().setValue(cookie.getMaxAge()).build()); + } + if (cookie.getSecure() != null) { + builder.setSecure(NullableTypes.NullableBool.newBuilder().setValue(cookie.getSecure()).build()); + } + if (cookie.getHttpOnly() != null) { + builder.setHttpOnly(NullableTypes.NullableBool.newBuilder().setValue(cookie.getHttpOnly()).build()); + } + if (cookie.getSameSite() != null) { + builder.setSameSite(cookie.getSameSite()); + } + return builder.build(); + } +}