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

feat(sdk): support custom port for router resource #335

Merged
merged 3 commits into from
Aug 23, 2024
Merged
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
8 changes: 8 additions & 0 deletions .changeset/unlucky-mails-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@plutolang/pluto-infra": patch
"@plutolang/pluto": patch
---

feat(sdk): add support for configuring host and port for router resource

This change introduces the ability to specify custom host and port settings for router resources, enhancing flexibility during local development.
4 changes: 4 additions & 0 deletions packages/base-py/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# pluto-base

## 0.0.8

chore: add some debug logs

## 0.0.7

feat: update resource ID generation to handle Unicode characters
Expand Down
7 changes: 7 additions & 0 deletions packages/base-py/pluto_base/resource.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from abc import ABC
from typing import Callable, List, Type

Expand Down Expand Up @@ -107,13 +108,19 @@ def __getattribute__(self, name: str):
# Check if the attribute is an infrastructure API, if it is, return an empty lambda.
infra_apis = self.__get_infra_apis()
if name in infra_apis:
if os.getenv("DEBUG", False):
print(f"Skipping infrastructure API: {name}")
return _empty_lambda

# Try to get the attribute from the client, if it doesn't exist, return the attribute of
# self. This is to make sure that the client is the first priority.
try:
if os.getenv("DEBUG", False):
print(f"Getting attribute from _client: {name}")
return getattr(self._client, name)
except:
# If the _client doesn't exist, or the attribute doesn't exist in the client, return the
# attribute of self.
if os.getenv("DEBUG", False):
print(f"Getting attribute from self: {name}")
return super().__getattribute__(name)
2 changes: 1 addition & 1 deletion packages/base-py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "pluto-base"
version = "0.0.7"
version = "0.0.8"
description = "The Base Library for Pluto Programming Language."
authors = ["Jade Zheng <[email protected]>"]
license = "Apache-2.0"
Expand Down
71 changes: 57 additions & 14 deletions packages/pluto-infra/src/simulator/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,81 @@ import { SimFunction } from "./function";

const VALID_HTTP_METHODS = ["get", "post", "put", "delete"];

/**
* Adapts the options to the correct names for TypeScript.
* The option names for TypeScript and Python are different, so this function converts Python-style
* option names to TypeScript-style option names.
*
* @param opts - The options object that may contain Python-style option names.
* @returns The adapted options object with TypeScript-style option names.
*/
function adaptOptions(opts?: any): RouterOptions | undefined {
if (opts === undefined) {
return;
}

if (opts.sim_host) {
opts.simHost = opts.sim_host;
}
if (opts.sim_port) {
opts.simPort = opts.sim_port;
}
return opts;
}

export class SimRouter implements IResourceInfra, IRouterInfra, IRouterClient {
public readonly id: string;

private readonly expressApp: express.Application;
private httpServer: http.Server;
private expressApp?: express.Express;
private httpServer?: http.Server;
private host: string;
private port: number;

public outputs: string;
public outputs?: string;

constructor(name: string, opts?: RouterOptions) {
opts = adaptOptions(opts);

this.id = genResourceId(Router.fqn, name);

this.host = opts?.simHost ?? "localhost";
this.port = parseInt(opts?.simPort ?? "0");
}

public async init() {
this.expressApp = express();
this.expressApp.use(cors());
this.expressApp.use(bodyParser.urlencoded({ extended: false }));
this.expressApp.use(bodyParser.json());

this.httpServer = this.expressApp.listen(0);
const address = this.httpServer.address();
if (address && typeof address !== "string") {
this.port = address.port;
} else {
throw new Error(`Failed to obtain the port for the router: ${name}`);
const httpServer = this.expressApp.listen(this.port, this.host);
this.httpServer = httpServer;

async function waitForServerReady() {
return await new Promise<number>((resolve, reject) => {
httpServer.on("listening", () => {
const address = httpServer.address();
if (address && typeof address !== "string") {
resolve(address.port);
} else {
reject(new Error(`Failed to obtain the port for the router`));
}
});

httpServer.on("error", (err) => {
console.error(err);
reject(err);
});
});
}
this.outputs = this.url();
const realPort = await waitForServerReady();
this.port = realPort;

name;
opts;
this.outputs = this.url();
}

public url(): string {
return `http://localhost:${this.port}`;
return `http://${this.host}:${this.port}`;
}

public async setup() {}
Expand Down Expand Up @@ -85,7 +128,7 @@ export class SimRouter implements IResourceInfra, IRouterInfra, IRouterClient {

const func = new SimFunction(closure);

this.expressApp[method](
this.expressApp![method](
path,
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
const reqPluto: HttpRequest = {
Expand Down
7 changes: 7 additions & 0 deletions packages/pluto-infra/src/simulator/website.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from "fs";
import http from "http";
import path from "path";
import cors from "cors";
import express from "express";
Expand Down Expand Up @@ -37,6 +38,7 @@ export class SimWebsite implements IResourceInfra, IWebsiteInfra, IWebsiteClient

private host: string;
private port: number;
private httpServer?: http.Server;

public outputs?: string;

Expand All @@ -57,6 +59,7 @@ export class SimWebsite implements IResourceInfra, IWebsiteInfra, IWebsiteClient
expressApp.use(express.static(this.websiteDir));

const httpServer = expressApp.listen(this.port, this.host);
this.httpServer = httpServer;

async function waitForServerReady() {
return await new Promise<number>((resolve, reject) => {
Expand Down Expand Up @@ -112,6 +115,10 @@ export class SimWebsite implements IResourceInfra, IWebsiteInfra, IWebsiteClient
if (this.originalPlutoJs) {
fs.writeFileSync(plutoJsPath, this.originalPlutoJs);
}

if (this.httpServer) {
this.httpServer.close();
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions packages/pluto-py/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# pluto-client

## 0.0.20

feat(sdk): add support for configuring host and port for website and router resource

This change introduces the ability to specify custom host and port settings for website and router resources, enhancing flexibility during local development.

## 0.0.19

feat: remove runtime dependency for AWS account ID retrieval
Expand Down
16 changes: 15 additions & 1 deletion packages/pluto-py/pluto_client/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,21 @@ class HttpResponse:

@dataclass
class RouterOptions:
pass
cors: Optional[bool] = None
"""
Currently, only support Vercel. If an invalid value is provided, or if no value is provided at
all, it will default to your specified platform.
"""
sim_host: Optional[str] = None
"""
Host address for simulating the website when running the project with `pluto run`. If not
provided, it will be `localhost`.
"""
sim_port: Optional[str] = None
"""
Port number for simulating the website when running the project with `pluto run`. If not
provided, it will be randomly assigned.
"""


class IRouterClientApi(resource.IResourceClientApi):
Expand Down
12 changes: 0 additions & 12 deletions packages/pluto-py/pluto_client/universal_class.py

This file was deleted.

7 changes: 4 additions & 3 deletions packages/pluto-py/pluto_client/website.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
IResourceInfraApi,
)

from .universal_class import UniversalClass
from .utils import create_simulator_client


@dataclass
Expand Down Expand Up @@ -67,7 +67,8 @@ def __init__(
from .clients import shared

self._client = shared.WebsiteClient(path, name, opts)
if platform_type in [PlatformType.Simulator]:
self._client = UniversalClass() # type: ignore
if platform_type == PlatformType.Simulator:
resource_id = utils.gen_resource_id(Website.fqn, name or "default")
self._client = create_simulator_client(resource_id) # type: ignore
else:
raise ValueError(f"not support this runtime '{platform_type}'")
2 changes: 1 addition & 1 deletion packages/pluto-py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "pluto-client"
version = "0.0.19"
version = "0.0.20"
description = "The Client Library for Pluto Programming Language."
authors = ["Jade Zheng <[email protected]>"]
license = "Apache-2.0"
Expand Down
12 changes: 12 additions & 0 deletions packages/pluto/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ export interface RouterOptions {
* and include the appropriate CORS headers in responses. @default true
*/
cors?: boolean;

/**
* Host address for simulating the website when running the project with `pluto run`. If not
* provided, it will be `localhost`.
*/
simHost?: string;

/**
* Port number for simulating the website when running the project with `pluto run`. If not
* provided, it will be a random port.
*/
simPort?: string;
}

/**
Expand Down
Loading