Skip to content

Commit

Permalink
Set-Cookie support in http (#208)
Browse files Browse the repository at this point in the history
* rpc http cookies

* take out log in nodejs worker

* Updated subtree from https://github.com/azure/azure-functions-language-worker-protobuf. Branch: dev. Commit:bd4379767d6c25be59dfbe91dc57f6c03af9968b Tag: v1.1.0

* fixde merged conflict

* add test

* addressing cr comments

* add comment

* added comments

* nit

* unit tests

* start of e2e tests

* tests and change behavior to throw instead of warn

* add pst for machines... really should never use expire

* http expires

*  test
  • Loading branch information
mhoeger authored Jun 18, 2019
1 parent 3beb8cb commit 933d353
Show file tree
Hide file tree
Showing 24 changed files with 826 additions and 165 deletions.
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";

// 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 {
// 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

0 comments on commit 933d353

Please sign in to comment.