Lesson 19-Deno Network Programming

Deno’s network programming capabilities are built on modern web standards, offering a comprehensive toolchain from low-level sockets to high-level HTTP services. Compared to Node.js, Deno’s network APIs are more modern, emphasizing security and consistency. This tutorial deeply explores Deno’s core network programming techniques, showcasing applications from basic to advanced through code examples.

Basic Network Programming

TCP/UDP Communication Basics

TCP Server Implementation

// tcp_server.ts
const listener = Deno.listen({ port: 8000 });
console.log("TCP server listening on 0.0.0.0:8000");

for await (const conn of listener) {
  handleConnection(conn);
}

async function handleConnection(conn: Deno.Conn) {
  try {
    const buffer = new Uint8Array(1024);
    const bytesRead = await conn.read(buffer);
    if (bytesRead === null) return;

    const message = new TextDecoder().decode(buffer.subarray(0, bytesRead));
    console.log("Received:", message);

    const response = `Echo: ${message}`;
    await conn.write(new TextEncoder().encode(response));
  } catch (error) {
    console.error("Connection error:", error);
  } finally {
    conn.close();
  }
}

// Run: deno run --allow-net tcp_server.ts

TCP Client Implementation

// tcp_client.ts
const conn = await Deno.connect({ hostname: "127.0.0.1", port: 8000 });
try {
  const message = "Hello Deno!";
  await conn.write(new TextEncoder().encode(message));

  const buffer = new Uint8Array(1024);
  const bytesRead = await conn.read(buffer);
  if (bytesRead !== null) {
    const response = new TextDecoder().decode(buffer.subarray(0, bytesRead));
    console.log("Server response:", response);
  }
} finally {
  conn.close();
}

// Run: deno run --allow-net tcp_client.ts

UDP Communication Example

UDP Server

// udp_server.ts
const socket = Deno.listenDatagram({ port: 3000, transport: "udp" });
console.log("UDP server listening on 0.0.0.0:3000");

for await (const event of socket) {
  if (event.kind === "message") {
    const [data, remoteAddr] = event;
    console.log(`Received from ${remoteAddr.hostname}:${remoteAddr.port}:`, 
      new TextDecoder().decode(data));

    // Echo message
    await socket.send(data, remoteAddr);
  }
}

// Run: deno run --allow-net udp_server.ts

UDP Client

// udp_client.ts
const socket = Deno.listenDatagram({ port: 0, transport: "udp" }); // Random port
const serverAddr = { hostname: "127.0.0.1", port: 3000 };

const message = new TextEncoder().encode("Hello UDP!");
await socket.send(message, serverAddr);

const [response] = await socket.receive();
console.log("Server response:", new TextDecoder().decode(response));

socket.close();

// Run: deno run --allow-net udp_client.ts

HTTP Protocol Implementation

HTTP Server Development

Basic HTTP Server

// http_server.ts
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";

const server = serve({ port: 8000 });
console.log("HTTP server running on http://localhost:8000");

for await (const req of server) {
  // Set response headers
  const headers = new Headers();
  headers.set("Content-Type", "text/plain");
  headers.set("X-Custom-Header", "Deno");

  // Build response
  req.respond({
    status: 200,
    headers,
    body: "Hello from Deno HTTP server!\n"
  });
}

// Run: deno run --allow-net http_server.ts

Routing Implementation

// http_router.ts
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";

const server = serve({ port: 8000 });

interface RouteHandler {
  (req: Request, params: Record<string, string>): Promise<Response>;
}

const routes: Record<string, RouteHandler> = {
  "/": handleRoot,
  "/hello": handleHello,
  "/user/:id": handleUser
};

async function handleRequest(req: Request): Promise<Response> {
  const url = new URL(req.url);
  const path = url.pathname;
  const method = req.method;

  // Simple route matching
  for (const [route, handler] of Object.entries(routes)) {
    const match = path.match(routeToRegex(route));
    if (match) {
      const params = extractParams(route, match);
      return handler(req, params);
    }
  }

  return new Response("404 Not Found", { status: 404 });
}

function routeToRegex(route: string): RegExp {
  return new RegExp("^" + route.replace(/:\w+/g, "([^/]+)") + "$");
}

function extractParams(route: string, match: RegExpMatchArray): Record<string, string> {
  const params: Record<string, string> = {};
  const paramNames = route.match(/:\w+/g) || [];

  paramNames.forEach((name, i) => {
    params[name.substring(1)] = match[i + 1];
  });

  return params;
}

async function handleRoot(req: Request, _params: Record<string, string>): Promise<Response> {
  return new Response("Welcome to Deno HTTP Server!\n");
}

async function handleHello(req: Request, _params: Record<string, string>): Promise<Response> {
  return new Response("Hello World!\n");
}

async function handleUser(req: Request, params: Record<string, string>): Promise<Response> {
  return new Response(`User ID: ${params.id}\n`);
}

for await (const req of server) {
  req.respond(await handleRequest(req));
}

// Run: deno run --allow-net http_router.ts

Advanced HTTP Client Usage

Request with Timeout

// http_client_timeout.ts
async function fetchWithTimeout(
  url: string,
  options: RequestInit & { timeout?: number } = {}
): Promise<Response> {
  const { timeout = 5000, ...fetchOptions } = options;

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      ...fetchOptions,
      signal: controller.signal
    });
    clearTimeout(timeoutId);
    return response;
  } catch (error) {
    clearTimeout(timeoutId);
    if (error instanceof DOMException && error.name === "AbortError") {
      throw new Error(`Request timed out after ${timeout}ms`);
    }
    throw error;
  }
}

// Usage example
try {
  const response = await fetchWithTimeout("https://httpbin.org/delay/3", { 
    timeout: 2000 
  });
  console.log(await response.text());
} catch (error) {
  console.error("Request failed:", error.message);
}

// Run: deno run --allow-net http_client_timeout.ts

File Upload Implementation

// http_file_upload.ts
async function uploadFile(
  url: string,
  filePath: string,
  fieldName: string = "file"
): Promise<Response> {
  const file = await Deno.open(filePath, { read: true });
  const fileStat = await Deno.stat(filePath);

  // Build FormData
  const formData = new FormData();
  formData.append(fieldName, file.readable);

  // Create request
  const request = new Request(url, {
    method: "POST",
    body: formData,
    headers: {
      "Content-Type": `multipart/form-data; boundary=${formData.boundary}`
    }
  });

  const response = await fetch(request);
  file.close();
  return response;
}

// Usage example
const response = await uploadFile(
  "https://httpbin.org/post",
  "./example.txt"
);
console.log(await response.json());

// Run: deno run --allow-read --allow-net http_file_upload.ts

WebSocket Communication

WebSocket Server

Basic WebSocket Server

// websocket_server.ts
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";

const server = serve({ port: 8080 });
console.log("WebSocket server running on ws://localhost:8080");

for await (const req of server) {
  if (req.headers.get("upgrade")?.toLowerCase() !== "websocket") {
    req.respond({ status: 400, body: "Not a WebSocket request\n" });
    continue;
  }

  // Upgrade WebSocket connection
  const { socket, response } = Deno.upgradeWebSocket(req);

  // WebSocket event handling
  socket.onopen = () => {
    console.log("Client connected");
    socket.send("Welcome to Deno WebSocket server!");
  };

  socket.onmessage = (e) => {
    console.log("Received message:", e.data);
    socket.send(`Echo: ${e.data}`);
  };

  socket.onclose = () => console.log("Client disconnected");
  socket.onerror = (e) => console.error("WebSocket error:", e);

  req.respondWith(response);
}

// Run: deno run --allow-net websocket_server.ts

WebSocket Client

Basic WebSocket Client

// websocket_client.ts
const socket = new WebSocket("ws://localhost:8080");

socket.onopen = () => {
  console.log("Connected to server");
  socket.send("Hello from client!");
};

socket.onmessage = (e) => {
  console.log("Server message:", e.data);
};

socket.onclose = () => console.log("Disconnected from server");
socket.onerror = (e) => console.error("WebSocket error:", e);

// Run: deno run --allow-net websocket_client.ts

Advanced Network Programming

HTTP/2 Implementation

HTTP/2 Server

// http2_server.ts
import { serveTls } from "https://deno.land/std@0.140.0/http/server.ts";

const server = serveTls({
  port: 443,
  certFile: "./cert.pem",
  keyFile: "./key.pem",
});

console.log("HTTP/2 server running on https://localhost:443");

for await (const req of server) {
  req.respond({
    status: 200,
    headers: new Headers({ "Content-Type": "text/plain" }),
    body: "Hello from HTTP/2 server!\n"
  });
}

// Note: Requires valid TLS certificate
// Run: deno run --allow-net --allow-read http2_server.ts

Proxy Server Implementation

HTTP Proxy Server

// proxy_server.ts
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";

const server = serve({ port: 8080 });
console.log("HTTP proxy server running on http://localhost:8080");

for await (const req of server) {
  // Handle CONNECT method (HTTPS proxy)
  if (req.method === "CONNECT") {
    handleConnectProxy(req);
  } else {
    handleHttpProxy(req);
  }
}

async function handleConnectProxy(req: Request) {
  const url = new URL(req.url);
  const [hostname, portStr] = url.hostname.split(":");
  const port = portStr ? parseInt(portStr) : 443;

  // Establish TCP connection to target server
  const tcpConn = await Deno.connect({ hostname, port });

  // Inform client connection established
  const response = new Response(null, { status: 200 });
  const writableStream = new WritableStream({
    write(chunk) {
      tcpConn.write(chunk);
    }
  });

  await response.body?.pipeTo(writableStream);
  req.respondWith(response);

  // Bidirectional data forwarding
  const [clientReader, clientWriter] = Object.values(req.body?.pipeThrough(
    new TransformStream()
  ) || {});

  const [serverReader, serverWriter] = Object.values(tcpConn.readable.pipeThrough(
    new TransformStream()
  ) || {});

  // Implement bidirectional data forwarding...
}

async function handleHttpProxy(req: Request) {
  // Parse target URL
  const url = new URL(req.url.replace(/^http:\/\/[^/]+/, ""));

  // Forward request
  const forwardedReq = new Request(url.href, {
    method: req.method,
    headers: req.headers,
    body: req.body
  });

  const response = await fetch(forwardedReq);
  req.respondWith(response);
}

// Run: deno run --allow-net proxy_server.ts

Network Security Practices

TLS/SSL Configuration

HTTPS Server

// https_server.ts
import { serveTls } from "https://deno.land/std@0.140.0/http/server.ts";

const server = serveTls({
  port: 443,
  certFile: "./cert.pem",
  keyFile: "./key.pem",
});

console.log("HTTPS server running on https://localhost:443");

for await (const req of server) {
  req.respond({
    status: 200,
    headers: new Headers({ "Content-Type": "text/plain" }),
    body: "Hello from HTTPS server!\n"
  });
}

// Run: deno run --allow-net --allow-read https_server.ts

Certificate Generation Command

# Generate self-signed certificate (for development)
openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout key.pem -out cert.pem -days 365 \
  -subj "/CN=localhost"

Security Protection Measures

Rate Limiting Implementation

// rate_limiter.ts
class RateLimiter {
  private requests: Map<string, number[]> = new Map();

  constructor(
    private windowMs: number = 60 * 1000, // 1-minute window
    private maxRequests: number = 100    // Maximum requests
  ) {}

  check(ip: string): boolean {
    const now = Date.now();
    const timestamps = this.requests.get(ip) || [];

    // Remove expired requests
    const validTimestamps = timestamps.filter(t => t > now - this.windowMs);

    if (validTimestamps.length >= this.maxRequests) {
      return false; // Exceeds limit
    }

    validTimestamps.push(now);
    this.requests.set(ip, validTimestamps);
    return true;
  }
}

// Usage example
const limiter = new RateLimiter();

for await (const req of serve({ port: 8000 })) {
  const ip = req.conn.remoteAddr.hostname;

  if (!limiter.check(ip)) {
    req.respond({ status: 429, body: "Too Many Requests\n" });
    continue;
  }

  req.respond({ status: 200, body: "OK\n" });
}

CORS Middleware

// cors_middleware.ts
function corsMiddleware(handler: (req: Request) => Promise<Response>) {
  return async (req: Request): Promise<Response> => {
    // Handle preflight request
    if (req.method === "OPTIONS") {
      return new Response(null, {
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
          "Access-Control-Allow-Headers": "Content-Type"
        }
      });
    }

    // Handle actual request
    const response = await handler(req);

    // Add CORS headers
    response.headers.set("Access-Control-Allow-Origin", "*");
    response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
    response.headers.set("Access-Control-Allow-Headers", "Content-Type");

    return response;
  };
}

// Usage example
const handler = corsMiddleware(async (req: Request) => {
  return new Response("CORS enabled response\n");
});

serve(handler, { port: 8000 });

Performance Optimization Techniques

Connection Pool Implementation

TCP Connection Pool

// connection_pool.ts
class ConnectionPool {
  private pool: Deno.Conn[] = [];
  private maxSize: number;
  private host: string;
  private port: number;

  constructor(host: string, port: number, maxSize: number = 10) {
    this.host = host;
    this.port = port;
    this.maxSize = maxSize;
  }

  async getConnection(): Promise<Deno.Conn> {
    // Try to get available connection from pool
    if (this.pool.length > 0) {
      return this.pool.pop()!;
    }

    // Pool empty, create new connection
    if (this.pool.length < this.maxSize) {
      return Deno.connect({ hostname: this.host, port: this.port });
    }

    // Pool full, wait for connection release
    throw new Error("Connection pool exhausted");
  }

  releaseConnection(conn: Deno.Conn) {
    if (this.pool.length < this.maxSize) {
      this.pool.push(conn);
    } else {
      conn.close(); // Pool full, close connection
    }
  }
}

// Usage example
const pool = new ConnectionPool("127.0.0.1", 8000, 5);

async function makeRequest() {
  const conn = await pool.getConnection();
  try {
    await conn.write(new TextEncoder().encode("PING"));
    const buffer = new Uint8Array(1024);
    const bytesRead = await conn.read(buffer);
    console.log("Response:", new TextDecoder().decode(buffer.subarray(0, bytesRead)));
  } finally {
    pool.releaseConnection(conn);
  }
}

// Run multiple requests to test connection pool
Array(10).fill(0).forEach(makeRequest);

Zero-Copy File Transfer

Efficient File Serving

// file_server_zero_copy.ts
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";

const server = serve({ port: 8000 });
console.log("Zero-copy file server running on http://localhost:8000");

for await (const req of server) {
  const filePath = `./files${req.url}`;

  try {
    // Get file info
    const fileInfo = await Deno.stat(filePath);
    if (fileInfo.isDirectory) {
      req.respond({ status: 400, body: "Directories not supported\n" });
      continue;
    }

    // Use send for zero-copy transfer
    const file = await Deno.open(filePath, { read: true });
    const response = new Response(file.readable, {
      headers: new Headers({
        "Content-Type": "application/octet-stream",
        "Content-Length": fileInfo.size.toString()
      })
    });

    req.respondWith(response);
    file.close();
  } catch (error) {
    if (error instanceof Deno.errors.NotFound) {
      req.respond({ status: 404, body: "File not found\n" });
    } else {
      console.error("Error:", error);
      req.respond({ status: 500, body: "Internal server error\n" });
    }
  }
}

// Run: deno run --allow-read --allow-net file_server_zero_copy.ts

Practical Case Study

Real-Time Chat Server

// chat_server.ts
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";

const server = serve({ port: 8080 });
console.log("Chat server running on ws://localhost:8080");

// Store all connected clients
const clients: WebSocket[] = [];

for await (const req of server) {
  if (req.headers.get("upgrade")?.toLowerCase() !== "websocket") {
    req.respond({ status: 400, body: "Not a WebSocket request\n" });
    continue;
  }

  const { socket, response } = Deno.upgradeWebSocket(req);
  clients.push(socket);

  console.log(`New client connected. Total: ${clients.length}`);

  socket.onmessage = (e) => {
    // Broadcast message to all clients
    for (const client of clients) {
      if (client !== socket && client.readyState === WebSocket.OPEN) {
        client.send(e.data);
      }
    }
  };

  socket.onclose = () => {
    const index = clients.indexOf(socket);
    if (index !== -1) {
      clients.splice(index, 1);
      console.log(`Client disconnected. Total: ${clients.length}`);
    }
  };

  socket.onerror = (e) => {
    console.error("WebSocket error:", e);
    socket.close();
  };

  req.respondWith(response);
}

// Run: deno run --allow-net chat_server.ts

Complementary WebSocket Client

// chat_client.ts
const socket = new WebSocket("ws://localhost:8080");

socket.onopen = () => {
  console.log("Connected to chat server");
  socket.send("Hello from client!");
};

socket.onmessage = (e) => {
  console.log("Received message:", e.data);
};

socket.onclose = () => console.log("Disconnected from server");
socket.onerror = (e) => console.error("WebSocket error:", e);

// Read input from console and send
Deno.stdin.setRawMode!(true);
for await (const line of Deno.stdin.readable.pipeThrough(
  new TextDecoderStream()
)) {
  if (line.trim()) {
    socket.send(line.trim());
  }
  if (line === "exit\n") break;
}

socket.close();
Share your love