Skip to content

Commit e1bbb55

Browse files
dprotasoLeo6Leo
andauthored
drop port forwarding (#6444)
* include a proxy endpoint that routes traffic to the node-server this simplifies client interaction as we don't require port-fowarding anymore * reduce frontend nextjs image size to ~300MB * use port 80 for frontend k8s service * include node about checking the tunnels for connectivity * fix non-websocket backend requests * sync frontend solution with changes * Minor tweaks to section 2 * fix setup.sh script and update solution.sh script * remove actual webhook url from docs * fix ports on env setup since we're not using localhost anymore * when creating the function include the type so invocation works * fix indentation and kubectl log line * don't use localhost anymore to access the frontend website * fix indentation on page 7 * quote the camel-k installation so it works on zsh * delete the correct unused image * use the default frontend image * Update code-samples/eventing/bookstore-sample-app/start/slack-sink/application.properties Co-authored-by: Leo Li <[email protected]> * shore up bash scripts * add log line to remind user to ensure tunnels/cloud-provider-kind is running * just suggest using KinD because it's networking is simpler * update get pods output to reflect kind * mention kind-registry port --------- Co-authored-by: Leo Li <[email protected]>
1 parent 585cf9a commit e1bbb55

32 files changed

+3326
-527
lines changed

code-samples/eventing/bookstore-sample-app/solution/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ Here's an overview of the components in the solution:
2323

2424
## Running the Solution
2525

26-
1. Have a locally running Kubernetes cluster e.g. [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) or [Minikube](https://minikube.sigs.k8s.io/docs/start).
27-
1. Have a locally running container registry, e.g. [Kind registry setup](https://kind.sigs.k8s.io/docs/user/local-registry/) or [Minikube registry setup](https://minikube.sigs.k8s.io/docs/handbook/registry/#enabling-insecure-registries).
26+
1. Have a locally running Kubernetes cluster e.g. [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation).
27+
1. Have a locally running container registry, e.g. [Kind registry setup](https://kind.sigs.k8s.io/docs/user/local-registry/).
2828
1. Install all the prerequisites and deploy the entire solution using the `solution.sh` script:
2929

3030
```sh

code-samples/eventing/bookstore-sample-app/solution/frontend/Dockerfile

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Use a base image with Node.js LTS
2-
FROM node:lts-alpine
2+
FROM node:lts-alpine AS builder
3+
4+
ENV NEXT_TELEMETRY_DISABLED=1
35

46
# Set the working directory inside the container
57
WORKDIR /app
@@ -16,8 +18,25 @@ COPY . .
1618
# Build the Next.js application
1719
RUN npm run build
1820

21+
FROM node:lts-alpine AS runner
22+
23+
WORKDIR /app
24+
25+
# Set environment variables for production
26+
ENV NODE_ENV=production
27+
ENV NEXT_TELEMETRY_DISABLED=1
28+
29+
# Automatically leverage output traces to reduce image size
30+
# https://nextjs.org/docs/advanced-features/output-file-tracing
31+
COPY --from=builder /app/public ./public
32+
COPY --from=builder /app/.next/standalone ./
33+
COPY --from=builder /app/.next/static ./.next/static
34+
1935
# Expose the port your app runs on
20-
EXPOSE 3000
36+
EXPOSE 8080
37+
38+
ENV PORT=8080
39+
2140

2241
# Define the command to run your app
23-
CMD ["npm", "run", "start"]
42+
CMD ["node", "server.js"]
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import {WebSocket, WebSocketServer} from 'ws';
2+
import { NextRequest, NextResponse } from 'next/server';
3+
4+
5+
export async function GET(
6+
req: NextRequest,
7+
{ params }: { params: Promise<{ slug: string }> }
8+
) {
9+
const { slug } = await params // 'a', 'b', or 'c'
10+
return proxyRequest(req, slug);
11+
}
12+
13+
export async function POST(
14+
req: NextRequest,
15+
{ params }: { params: Promise<{ slug: string }> }
16+
) {
17+
const { slug } = await params // 'a', 'b', or 'c'
18+
return proxyRequest(req, slug);
19+
}
20+
21+
export async function PUT(
22+
req: NextRequest,
23+
{ params }: { params: Promise<{ slug: string }> }
24+
) {
25+
const { slug } = await params // 'a', 'b', or 'c'
26+
return proxyRequest(req, slug);
27+
}
28+
29+
export async function DELETE(
30+
req: NextRequest,
31+
{ params }: { params: Promise<{ slug: string }> }
32+
) {
33+
const { slug } = await params // 'a', 'b', or 'c'
34+
return proxyRequest(req, slug);
35+
}
36+
37+
async function proxyRequest(req: NextRequest, slug: string) {
38+
const BACKEND_URL = (process.env.BACKEND_URL || `http://node-server-svc.${process.env.POD_NAMESPACE}.svc.cluster.local`) + '/' + slug;
39+
40+
const backendResponse = await fetch(BACKEND_URL, {
41+
method: req.method,
42+
headers: {
43+
...Object.fromEntries(req.headers),
44+
host: new URL(BACKEND_URL).host,
45+
},
46+
body: req.body ? req.body : undefined,
47+
duplex: "half",
48+
} as any);
49+
50+
// Clone the response and return it
51+
const data = await backendResponse.arrayBuffer();
52+
return new NextResponse(data, {
53+
status: backendResponse.status,
54+
headers: backendResponse.headers,
55+
});
56+
}
57+
58+
export function UPGRADE(
59+
client: WebSocket,
60+
server: WebSocketServer,
61+
request: NextRequest,
62+
context: import('next-ws/server').RouteContext<'/backend/[slug]'>,
63+
) {
64+
const slug = context.params?.slug
65+
console.log('Client connected to /backend/' + slug)
66+
67+
// Backend server URL - replace with your actual backend server
68+
const BACKEND_URL = (process.env.BACKEND_WS_URL || `ws://node-server-svc.${process.env.POD_NAMESPACE}.svc.cluster.local`) + '/' + slug;
69+
70+
console.log('Backend URL:', BACKEND_URL);
71+
72+
let backendConnection: WebSocket | null = null;
73+
let isClientClosed = false;
74+
let isBackendClosed = false;
75+
76+
// Connect to backend server
77+
try {
78+
backendConnection = new WebSocket(BACKEND_URL);
79+
80+
backendConnection.on('open', () => {
81+
console.log('Connected to backend server');
82+
});
83+
84+
backendConnection.on('message', (message) => {
85+
if (!isClientClosed) {
86+
client.send(message.toString());
87+
}
88+
});
89+
90+
backendConnection.on('close', (err) => {
91+
console.log('Backend connection closed', err);
92+
isBackendClosed = true;
93+
if (!isClientClosed) {
94+
client.close();
95+
}
96+
});
97+
98+
backendConnection.on('error', (error) => {
99+
console.error('Backend connection error:', error);
100+
if (!isClientClosed) {
101+
client.close();
102+
}
103+
});
104+
105+
} catch (error) {
106+
console.error('Failed to connect to backend:', error);
107+
client.close();
108+
return;
109+
}
110+
111+
// Handle messages from client
112+
client.on('message', (message) => {
113+
if (backendConnection && !isBackendClosed) {
114+
backendConnection.send(message.toString());
115+
}
116+
});
117+
118+
// Handle client disconnect
119+
client.once('close', () => {
120+
console.log('A client disconnected');
121+
isClientClosed = true;
122+
if (backendConnection && !isBackendClosed) {
123+
backendConnection.close();
124+
}
125+
});
126+
127+
// Handle client errors
128+
client.on('error', (error) => {
129+
console.error('Client connection error:', error);
130+
isClientClosed = true;
131+
if (backendConnection && !isBackendClosed) {
132+
backendConnection.close();
133+
}
134+
});
135+
}

code-samples/eventing/bookstore-sample-app/solution/frontend/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default function RootLayout({
1818
children: React.ReactNode;
1919
}>) {
2020
return (
21-
<html lang='en'>
21+
<html lang='en' suppressHydrationWarning>
2222
<body className={inter.className}>{children}</body>
2323
</html>
2424
);

code-samples/eventing/bookstore-sample-app/solution/frontend/client/components/CommentForm.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ const CommentForm = () => {
164164
setEverSubmit(true);
165165
setResponseSuccess("unknown");
166166

167-
fetch("http://localhost:8080/add", {
167+
fetch(`http://${window.location.host}/backend/add`, {
168168
method: "POST",
169169
headers: {
170170
"Content-Type": "application/json",

code-samples/eventing/bookstore-sample-app/solution/frontend/client/components/CommentList.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const CommentList = ({setStatus}) => {
55
const [comments, setComments] = useState([]);
66

77
useEffect(() => {
8-
const ws = new WebSocket("ws://localhost:8080/comments");
8+
const ws = new WebSocket(`ws://${window.location.host}/backend/comments`);
99

1010
ws.onmessage = (event) => {
1111
const newComments = JSON.parse(event.data);

code-samples/eventing/bookstore-sample-app/solution/frontend/config/100-front-end-deployment.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ spec:
1818
- name: bookstore-frontend
1919
image: ghcr.io/knative/bookstore-frontend:latest
2020
ports:
21-
- containerPort: 3000
21+
- containerPort: 8080
22+
env:
23+
- name: POD_NAMESPACE
24+
valueFrom:
25+
fieldRef:
26+
fieldPath: metadata.namespace
2227

2328
---
2429
apiVersion: v1
@@ -27,7 +32,8 @@ metadata:
2732
name: bookstore-frontend-svc
2833
spec:
2934
ports:
30-
- port: 3000
35+
- port: 80
36+
targetPort: 8080
3137
selector:
3238
app: bookstore-frontend
3339
type: LoadBalancer
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/** @type {import('next').NextConfig} */
2-
const nextConfig = {};
2+
const nextConfig = {
3+
output: 'standalone',
4+
};
35

46
export default nextConfig;

0 commit comments

Comments
 (0)