Skip to content

feat: support set multiple cookies in http response #791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<String, String> headers;
private List<RpcHttpCookie> 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);
Expand All @@ -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;
}
}
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();
}
}