Skip to content
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

Set-Cookie support in http #208

Merged
merged 15 commits into from
Jun 18, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package AzureFunctionsRpcMessages;

import "google/protobuf/duration.proto";
import "identity/ClaimsIdentityRpc.proto";
import "shared/NullableTypes.proto";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// Interface exported by the server.
service FunctionRpc {
Expand Down Expand Up @@ -375,6 +376,44 @@ message RpcException {
string message = 2;
}

// Http cookie type. Note that only name and value are used for Http requests
message RpcHttpCookie {
mhoeger marked this conversation as resolved.
Show resolved Hide resolved
// Enum that lets servers require that a cookie shouoldn't be sent with cross-site requests
enum SameSite {
None = 0;
Lax = 1;
Strict = 2;
}

// Cookie name
string name = 1;

// Cookie value
string value = 2;

// Specifies allowed hosts to receive the cookie
NullableString domain = 3;

// Specifies URL path that must exist in the requested URL
NullableString path = 4;

// Sets the cookie to expire at a specific date instead of when the client closes.
// It is generally recommended that you use "Max-Age" over "Expires".
NullableTimestamp expires = 5;

// Sets the cookie to only be sent with an encrypted request
NullableBool secure = 6;

// Sets the cookie to be inaccessible to JavaScript's Document.cookie API
NullableBool http_only = 7;

// Allows servers to assert that a cookie ought not to be sent along with cross-site requests
SameSite same_site = 8;

// Number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately.
NullableDouble max_age = 9;
}

// TODO - solidify this or remove it
message RpcHttp {
string method = 1;
Expand All @@ -387,4 +426,5 @@ message RpcHttp {
bool enable_content_negotiation= 16;
TypedData rawBody = 17;
repeated RpcClaimsIdentity identities = 18;
repeated RpcHttpCookie cookies = 19;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
syntax = "proto3";
// protobuf vscode extension: https://marketplace.visualstudio.com/items?itemName=zxh404.vscode-proto3

import "shared/NullableString.proto";
import "shared/NullableTypes.proto";

// Light-weight representation of a .NET System.Security.Claims.ClaimsIdentity object.
// This is the same serialization as found in EasyAuth, and needs to be kept in sync with
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
syntax = "proto3";
// protobuf vscode extension: https://marketplace.visualstudio.com/items?itemName=zxh404.vscode-proto3

import "google/protobuf/timestamp.proto";

message NullableString {
oneof string {
string value = 1;
}
}

message NullableDouble {
oneof double {
double value = 1;
}
}

message NullableBool {
oneof bool {
bool value = 1;
}
}

message NullableTimestamp {
oneof timestamp {
google.protobuf.Timestamp value = 1;
}
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions src/Context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FunctionInfo } from './FunctionInfo';
import { fromRpcHttp, fromTypedData, getNormalizedBindingData, getBindingDefinitions } from './Converters';
import { fromRpcHttp, fromTypedData, getNormalizedBindingData, getBindingDefinitions } from './converters';
import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc';
import { Request, RequestProperties } from './http/Request';
import { Response } from './http/Response';
Expand Down Expand Up @@ -59,7 +59,8 @@ class InvocationContext implements Context {
this.bindings = {};
let _done = false;
let _promise = false;


// Log message that is tied to function invocation
this.log = Object.assign(
<ILog>(...args: any[]) => logWithAsyncCheck(_done, logCallback, LogLevel.Information, executionContext, ...args),
{
Expand All @@ -86,6 +87,7 @@ class InvocationContext implements Context {
}
_done = true;

// Allow HTTP response from context.res if HTTP response is not defined from the context.bindings object
if (info.httpOutputName && this.res && this.bindings[info.httpOutputName] === undefined) {
this.bindings[info.httpOutputName] = this.res;
}
Expand Down
136 changes: 0 additions & 136 deletions src/Converters.ts

This file was deleted.

9 changes: 8 additions & 1 deletion src/FunctionInfo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc';
import { toTypedData, toRpcHttp } from './Converters';
import { toTypedData, toRpcHttp } from './converters';

const returnBindingKey = "$return";

export class FunctionInfo {
public name: string;
Expand Down Expand Up @@ -35,4 +37,9 @@ export class FunctionInfo {
});
}
}

/** Return output binding details on the special key "$return" output binding */
public getReturnBinding() {
return this.outputBindings[returnBindingKey];
}
}
30 changes: 18 additions & 12 deletions src/WorkerChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Status = rpc.StatusResult.Status;
import { IFunctionLoader } from './FunctionLoader';
import { CreateContextAndInputs, LogCallback, ResultCallback } from './Context';
import { IEventStream } from './GrpcService';
import { toTypedData } from './Converters';
import { toTypedData } from './converters';
import { systemError, systemLog } from './utils/Logger';

/**
Expand Down Expand Up @@ -137,18 +137,24 @@ export class WorkerChannel implements IWorkerChannel {
invocationId: msg.invocationId,
result: this.getStatus(err)
}
if (result) {
if (result.return) {
response.returnValue = toTypedData(result.return);
}
if (result.bindings) {
response.outputData = Object.keys(info.outputBindings)
.filter(key => result.bindings[key] !== undefined)
.map(key => <rpc.IParameterBinding>{
name: key,
data: info.outputBindings[key].converter(result.bindings[key])
});

try {
if (result) {
if (result.return) {
let returnBinding = info.getReturnBinding();
response.returnValue = returnBinding ? returnBinding.converter(result.return) : toTypedData(result.return);
}
if (result.bindings) {
response.outputData = Object.keys(info.outputBindings)
.filter(key => result.bindings[key] !== undefined)
.map(key => <rpc.IParameterBinding>{
name: key,
data: info.outputBindings[key].converter(result.bindings[key])
});
}
}
} catch (e) {
response.result = this.getStatus(e)
}

this._eventStream.write({
Expand Down
58 changes: 58 additions & 0 deletions src/converters/BindingConverters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc';
import { FunctionInfo } from '../FunctionInfo';
import { Dict } from '../Context';
import { BindingDefinition } from '../public/Interfaces';
import { fromTypedData } from './RpcConverters';

type BindingDirection = 'in' | 'out' | 'inout' | undefined;

export function getBindingDefinitions(info: FunctionInfo): BindingDefinition[] {
let bindings = info.bindings;
if (!bindings) {
return [];
}

return Object.keys(bindings)
.map(name => { return {
name: name,
type: bindings[name].type || "",
direction: getDirectionName(bindings[name].direction)
};
});
}

export function getNormalizedBindingData(request: rpc.IInvocationRequest): Dict<any> {
let bindingData: Dict<any> = {
invocationId: request.invocationId
};
// node binding data is camel cased due to language convention
if (request.triggerMetadata) {
Object.assign(bindingData, convertKeysToCamelCase(request.triggerMetadata))
}
return bindingData;
}

function getDirectionName(direction: rpc.BindingInfo.Direction|null|undefined): BindingDirection {
let directionName = Object.keys(rpc.BindingInfo.Direction).find(k => rpc.BindingInfo.Direction[k] === direction);
return isBindingDirection(directionName)? directionName as BindingDirection : undefined;
}

function isBindingDirection(input: string | undefined): boolean {
return (input == 'in' || input == 'out' || input == 'inout')
}

// Recursively convert keys of objects to camel case
function convertKeysToCamelCase(obj: any) {
var output = {};
for (var key in obj) {
let value = fromTypedData(obj[key]) || obj[key];
let camelCasedKey = key.charAt(0).toLocaleLowerCase() + key.slice(1);
// If the value is a JSON object (and not array and not http, which is already cased), convert keys to camel case
if (!Array.isArray(value) && typeof value === 'object' && value && value.http == undefined) {
output[camelCasedKey] = convertKeysToCamelCase(value);
} else {
output[camelCasedKey] = value;
}
}
return output;
}
Loading