# HG changeset patch # User June Park # Date 1759190438 25200 # Node ID 114cad94008fed13371a7bc251d3dc5c918dd490 # Parent 1e61008b9980a9277ea2a0250f5435d847741ab7 [Seobeo] Updated to support thread and edge server calls. diff -r 1e61008b9980 -r 114cad94008f dowa/test_folder/bar/foo.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dowa/test_folder/bar/foo.txt Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,1 @@ +hellow diff -r 1e61008b9980 -r 114cad94008f mrjunejune/BUILD --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/BUILD Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,14 @@ +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +filegroup( + name = "pages_files", + srcs = glob(["pages/**"]), +) + +cc_binary( + name = "mrjunejune", + srcs = ["main.c"], + deps = ["//seobeo:seobeo"], + data = glob(["pages/**"]) + ["//seobeo:seobeo_headers"], +) diff -r 1e61008b9980 -r 114cad94008f mrjunejune/main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/main.c Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,18 @@ +/** + * + * MrJuneJune + * + * - Server side generated my blog created using entirely in C. + * - Resume + * - What have I done? + * - Blogs.... + * + */ + +#include "seobeo/seobeo.h" + + +int main(void) +{ + Seobeo_Web_StartBasicHTTPServer("mrjunejune/pages", "6969", SEOBEO_MODE_FORK, 2); +} diff -r 1e61008b9980 -r 114cad94008f mrjunejune/pages/base.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/pages/base.css Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,210 @@ +/* Base Colors */ +:root { + --white: #ffffff; + --black: hsl(224, 12%, 4%); + --darkgray: #333333; + --gray: #5d5d5d; + --lightgray: #999999; + --green: #c2e15f; + --orange: #fda333; + --purple: #d3a4f9; + --red: #fb4485; + --blue: #6ce0f1; + --darktext: #414141; + /* from astro */ + --accent: #2337ff; + --accent-dark: #000d8a; + --gray: 96, 115, 159; + --gray-light: 229, 233, 240; + --gray-dark: 34, 41, 57; + --gray-gradient: rgba(var(--gray-light), 50%), #fff; + --box-shadow: 0 2px 6px rgba(var(--gray), 25%), + 0 8px 24px rgba(var(--gray), 33%), 0 16px 32px rgba(var(--gray), 33%); + --awesome: #dc3522; + --main-width: 720px; +} +.dark { + --white: hsl(224, 10%, 10%); + --black: hsl(0, 0%, 90%); + --darkgray: #cccccc; /* Inverted */ + --lightgray: #666666; /* Inverted */ + --green: #3d1ea0; /* Complementary */ + --orange: #025ccc; /* Complementary */ + --purple: #2b5b06; /* Complementary */ + --red: #04bb7a; /* Complementary */ + --blue: #932f0e; /* Complementary */ + --darktext: #bebebe; /* Inverted */ + --text: var(--lightgray); /* Opposite of --darkgray */ + --graytext: var(--lightgray); /* Opposite of --gray */ + --lighttext: var(--darkgray); /* Opposite of --lightgray */ + --awesome: #23cade; /* Complementary */ + + /* Opposite of Astro Colors */ + --accent: #ffcc00; /* Complementary */ + --accent-dark: #ffb275; /* Modified complementary */ + --gray: 159, 140, 96; /* Inverted */ + --gray-light: 26, 22, 15; /* Inverted */ + --gray-dark: 221, 214, 198; /* Inverted */ + --gray-gradient: rgba(26, 22, 15, 50%), #000; /* Adjusted */ + --box-shadow: 0 -2px -6px rgba(159, 140, 96, 25%), + 0 -8px -24px rgba(159, 140, 96, 33%), 0 -16px -32px rgba(159, 140, 96, 33%); +} + +html { + background: var(--white); +} + +/* fonts */ +@font-face { + font-family: "Atkinson"; + src: url("/fonts/atkinson-regular.woff") format("woff"); + font-weight: 400; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: "Atkinson"; + src: url("/fonts/atkinson-bold.woff") format("woff"); + font-weight: 700; + font-style: normal; + font-display: swap; +} +/* Fonts */ +@font-face { + font-family: "Roboto"; + src: url("/Roboto-Regular.ttf"); +} + +@font-face { + font-family: "Roboto Light"; + src: url("/Roboto-Thin.ttf"); +} + +a { + color: inherit; /* blue colors for links too */ + text-decoration: inherit; /* no underline */ +} + +body { + font-family: "Atkinson", sans-serif; + margin: 0; + padding: 0; + text-align: left; + background: var(--gray-gradient); + background-size: 100% 600px; + word-wrap: break-word; + overflow-wrap: break-word; + color: rgb(var(--gray-dark)); + font-size: 20px; + line-height: 1.7; +} +main { + background: var(--white); + width: var(--main-width); + max-width: calc(100% - 2em); + margin: auto; + padding: 1em 1em; +} +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 0 0.5rem 0; + color: rgb(var(--black)); + line-height: 1.2; +} +h1 { + font-size: 3.052em; +} +h2 { + font-size: 2.441em; +} +h3 { + font-size: 1.953em; +} +h4 { + font-size: 1.563em; +} +h5 { + font-size: 1.25em; +} +strong, +b { + font-weight: 700; +} +p { + margin-bottom: 1em; +} +.prose p { + margin-bottom: 2em; +} +textarea { + width: 100%; + font-size: 16px; +} +input { + font-size: 16px; +} +table { + width: 100%; +} +img { + max-width: 100%; + height: auto; + border-radius: 8px; +} +code { + padding: 2px 5px; + background-color: rgb(var(--gray-light)); + border-radius: 2px; +} +pre { + padding: 1.5em; + border-radius: 8px; +} +pre > code { + all: unset; +} +blockquote { + border-left: 4px solid var(--accent); + padding: 0 0 0 20px; + margin: 0px; + font-size: 1.333em; +} +hr { + border: none; + border-top: 1px solid rgb(var(--gray-light)); +} +@media (max-width: 720px) { + body { + font-size: 18px; + } + main { + padding: 1em; + } +} + +.sr-only { + border: 0; + padding: 0; + margin: 0; + position: absolute !important; + height: 1px; + width: 1px; + overflow: hidden; + /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */ + clip: rect(1px 1px 1px 1px); + /* maybe deprecated but we need to support legacy browsers */ + clip: rect(1px, 1px, 1px, 1px); + /* modern browsers, clip-path works inwards from each corner */ + clip-path: inset(50%); + /* added line to stop words getting smushed together (as they go onto separate lines and some screen readers do not understand line feeds as a space */ + white-space: nowrap; +} + +.center { + display: flex; + justify-content: center; +} diff -r 1e61008b9980 -r 114cad94008f mrjunejune/pages/fonts/Roboto-Regular.ttf Binary file mrjunejune/pages/fonts/Roboto-Regular.ttf has changed diff -r 1e61008b9980 -r 114cad94008f mrjunejune/pages/fonts/Roboto-Thin.ttf Binary file mrjunejune/pages/fonts/Roboto-Thin.ttf has changed diff -r 1e61008b9980 -r 114cad94008f mrjunejune/pages/fonts/atkinson-bold.woff Binary file mrjunejune/pages/fonts/atkinson-bold.woff has changed diff -r 1e61008b9980 -r 114cad94008f mrjunejune/pages/fonts/atkinson-regular.woff Binary file mrjunejune/pages/fonts/atkinson-regular.woff has changed diff -r 1e61008b9980 -r 114cad94008f mrjunejune/pages/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/pages/index.html Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,240 @@ + + + + + + + + +
+
+

Hi, my name is Juntae, but most people call me June.

+

I am a software engineer with experience spanning a wide range of companies, from small startups to FAANG.

+

Feel free to check out my resume below, and if you're interested, don’t hesitate to contact me for contract work ranging from web/app development to embedded programming.

+
+
+

JUNTAE PARK

+

FULL STACK DEVELOPER · SOFTWARE ENGINEER

+
+ Bay Area, CA, USA +
+
+

📱(US) 650-531-1728 |📱(CA) 437-580-8026 | ✉️ parkjune1995@gmail.com | + mrjunejune + | + + + + junepark + +

+
+
+ +
+

Summary

+
+
+

Software Engineer with 8 years of hands-on experience across diverse tech stacks, from early-stage startups to FANG-scale systems. Adept in designing and delivering robust software solutions using modern languages, frameworks, and cloud platforms. Open to impactful work.

+
+ +

Skills

+
+
+
+

+ Programming Languages: + TypeScript, Python, C++/C, Ruby, Java, MATLAB +

+

+ Tools & Platforms: + Bazel, PostgresSQL, Mercurial, Git, Pands, Raylib, XCode +

+

+ Web Frameworks: + Django, Rails, React, Flask +

+

+ DevOp: + Plummi, Heroku, DigitalOcean, AWS, Google Cloud +

+

+ Language: + English, Korean, Japanese +

+
+ + +
+

Experience

+
+
+
+

+ Meta +

+

San Francisco, CA, USA

+
+
+

SOFTWARE ENGINEER

+ +
+
    +
  • + Took initiative on Channel Value Rule, targeting the 16% of ad traffic with both app and web destinations to improve value attribution and ROI. +
  • +
  • + Built full-stack features using React and Hack/GraphQL, contributing to scalable, production-ready systems. +
  • +
  • + Partnered with data science to design A/B tests and analyze revenue impact of ads destination. +
  • +
  • + Proposed and implemented alpha improvements to internal testing infrastructure, reducing test time by 50% and enhancing developer velocity. +
  • +
+
+

+ Warner Music Group +

+

Toronto, ON, Canada

+
+
+

TECHNICAL LEAD ENGINEER

+ +
+
    +
  • + Implements bazel structure for the company for TypeScript and JavaScript code base for hermiticity and stablishing standards for JavaScript and +
  • +
  • + TypeScript testing and code structures. +
  • +
  • + Led a team of five engineers in building GraphQL endpoints for client-facing applications using Apollo and AppSync, supporting over 2000 RPS and auto scaling depending on request values. +
  • +
  • + Improved application response times by up to 85% for graphQL response by updating database schema and SQL queries, eliminating N+1 queries and lack of indexes. +
  • +
  • + Developed CI/CD pipelines for backend structures. +
  • +
  • + Designed infrastructure for pub/sub, caching, and media processing logic. +
  • +
+ +
+

+ Google +

+

Toronto, ON, Canada

+
+
+

SOFTWARE ENGINEER

+ +
+
    +
  • + Implements and maintained new features relating to App Script across google workspace platform including Gmail, sheets, and Docs.
  • +
  • + Improved a response time and render time of App Script hover card components.
  • +
  • + Collaborated with a team of developers to ensure timely and accurate delivery of features.
  • +
  • + Conducted user testing and gathered feedback to iterate on features for optimal user experience.
  • +
+ +
+

+ Everlywell +

+

Toronto, ON, Canada

+
+
+

SOFTWARE ENGINEER

+ +
+
    +
  • + Maintained Amazon amplify apps to create and deploy React web applications for companies such as NBA, Tinder, and other companies for COVID-19 at-home test kits.
  • +
  • + Implemented a script that helps accurately access and refund unused covid test kits; helping company save up to 200,000 USD.
  • +
  • + Created several Rails controllers for internal purposes; mocking end to end user experience for QA, mass refund features for CX department, and more, ultimately reducing support tickets amount by 50 percent.
  • +
  • + Implemented an audit table to help debug problems and logged which process was responsible for the change of the record using PaperTrail gems
  • +
+ +
+

+ Spiria +

+

Oakville, ON, Canada

+
+
+

SOFTWARE ENGINEER

+ +
+
    +
  • + Constructed RESTful API endpoints in multiple different frameworks such as Django, Ruby on Rails, and Flask and automated API documentation process using swagger. +
  • +
  • + Designed custom rake tasks for importing production data into newly updated data structure to meet client's needs. +
  • +
  • + Maintained or updated staging/productions servers. Debugged problems in production postgres database using ssh and postgres console on Heroku or AWS servers +
  • +
  • + Collaborated in creating automation python scripts for websites and application using selenium covering for QA eliminating 80% of QA's manual work +
  • +
+ +
+

+ Apex Score +

+

Oakville, ON, Canada

+
+
+

SOFTWARE ENGINEER

+ +
+
    +
  • + Developed custom Shapley value regression model to calculate importance of independent variables of data sets using sklearn, pandas, and numpy. +
  • +
  • + Created custom image uploader to Amazon s3 bucket using boto3 library. +
  • +
  • + Built RESTful API application using Flask framework and automated extensive API documentation pages using flask-restplus, pytest, and swagger, covering 95% of the code base. +
  • +
  • + Created an interactive graph using D3.js in Vue.js with data from Flask backend API. +
  • +
+ +
+

Education

+
+
+
+

+ University of British Columbia +

+

Kelowna, British Columbia

+
+
+

BACHELOR OF SCIENCE IN PHYSICS

+ +
+
+ + diff -r 1e61008b9980 -r 114cad94008f mrjunejune/pages/resume.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/pages/resume.css Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,161 @@ +:root { + --text: var(--black); + --graytext: var(--gray); + --lighttext: var(--lightgray); +} + +.sub-header { + display: flex; + margin-top: 20px; + align-items: center; + gap: 5px; +} + +.line { + flex-grow: 1; + border-bottom: 1px solid #333; /* Adjust color and thickness as needed */ +} + +.header-firstname-style { + font-size: 32pt; + font-family: "Roboto Light", sans-serif; + color: var(--graytext); +} + +.header-lastname-style { + font-size: 32pt; + font-family: "Roboto", sans-serif; + font-weight: bold; + color: var(--text); +} + +.header-position-style { + font-size: 7.6pt; + font-family: "Source Sans Pro", sans-serif; + font-variant: small-caps; + color: var(--awesome); +} + +.header-address-style { + font-size: 8pt; + font-family: "Roboto", sans-serif; + font-style: italic; + color: var(--lighttext); +} + +.header-social-style { + font-size: 10pt; + font-family: "Roboto", sans-serif; + color: var(--text); +} + +.header-quote-style { + font-size: 9pt; + font-family: "Source Sans Pro", sans-serif; + font-style: italic; + color: var(--darktext); +} + +.footer-style { + font-size: 8pt; + font-family: "Source Sans Pro", sans-serif; + font-variant: small-caps; + color: var(--lighttext); +} + +.section-style { + font-size: 16pt; + font-family: "Source Sans Pro", sans-serif; + font-weight: bold; + color: var(--text); +} + +.subsection-style { + font-size: 12pt; + font-family: "Source Sans Pro", sans-serif; + font-variant: small-caps; + color: var(--text); +} + +.paragraph-style { + font-size: 9pt; + font-family: "Source Sans Pro Light", sans-serif; + color: var(--text); +} + +.entry-title-style { + font-size: 10pt; + font-family: "Source Sans Pro", sans-serif; + font-weight: bold; + color: var(--darktext); +} + +.entry-position-style { + font-size: 8pt; + font-family: "Source Sans Pro", sans-serif; + font-variant: small-caps; + color: var(--graytext); +} + +.entry-date-style { + font-size: 8pt; + font-family: "Source Sans Pro Light", sans-serif; + font-style: italic; + color: var(--graytext); +} + +.entry-location-style { + font-size: 9pt; + font-family: "Source Sans Pro Light", sans-serif; + font-style: italic; + color: var(--awesome); +} + +.description-style { + font-size: 9pt; + font-family: "Source Sans Pro Light", sans-serif; + color: var(--text); +} + +.subentry-title-style { + font-size: 8pt; + font-family: "Source Sans Pro", sans-serif; + color: var(--graytext); +} + +.subentry-position-style { + font-size: 7pt; + font-family: "Source Sans Pro", sans-serif; + font-variant: small-caps; + color: var(--graytext); +} + +.honor-title-style, +.honor-position-style, +.honor-date-style, +.honor-location-style, +.skill-type-style, +.skill-set-style { + font-size: 9pt; + font-family: "Source Sans Pro", sans-serif; + color: var(--graytext); +} + +.letter-section-style, +.recipient-title-style, +.letter-title-style, +.letter-date-style, +.letter-text-style, +.letter-name-style, +.letter-enclosure-style { + font-family: "Source Sans Pro", sans-serif; +} + +.info { + text-align: center; +} + +.flex-box { + display: flex; + justify-content: space-between; +} diff -r 1e61008b9980 -r 114cad94008f seobeo/BUILD --- a/seobeo/BUILD Mon Sep 29 16:00:44 2025 -0700 +++ b/seobeo/BUILD Mon Sep 29 17:00:38 2025 -0700 @@ -1,12 +1,14 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_library.bzl", "cc_library") -alias( - name = "seobeo", - actual = select({ - "@platforms//os:osx": ":seobeo_example_mac", - }), - visibility = ["//visibility:public"], +config_setting( + name = "macos", + constraint_values = ["@platforms//os:osx"], +) + +config_setting( + name = "linux", + constraint_values = ["@platforms//os:linux"], ) filegroup( @@ -18,35 +20,55 @@ name = "seobeo_headers", srcs = [ "seobeo.h", - "seobeo_internal.h" + "seobeo_internal.h", ], visibility = ["//visibility:public"], ) cc_binary( - name = "seobeo_example_mac", + name = "seobeo_example", srcs = ["main.c"], - deps = [":seobeo_non_window"], - data = glob(["pages/**"]), + deps = [":seobeo"], + data = [":pages_files"], +) + +alias( + name = "seobeo", + actual = select({ + ":macos": ":seobeo_macos", + ":linux": ":seobeo_linux", + "//conditions:default": ":seobeo_linux", + }), + visibility = ["//visibility:public"], +) + +cc_library( + name = "seobeo_macos", + srcs = [ + "s_linux_network.c", + "s_web.c", + "os/s_macos_edge.c", + ], + hdrs = [":seobeo_headers"], + deps = ["//dowa:dowa"], target_compatible_with = [ - "@platforms//os:osx", + "@platforms//os:osx", ], visibility = ["//visibility:public"], ) cc_library( - name = "seobeo_non_window", + name = "seobeo_linux", srcs = [ "s_linux_network.c", "s_web.c", + "os/s_linux_edge.c", ], + hdrs = [":seobeo_headers"], deps = ["//dowa:dowa"], - hdrs = [ - "seobeo.h", - "seobeo_internal.h" - ], target_compatible_with = [ - "@platforms//os:osx", + "@platforms//os:linux", ], visibility = ["//visibility:public"], ) + diff -r 1e61008b9980 -r 114cad94008f seobeo/example.c --- a/seobeo/example.c Mon Sep 29 16:00:44 2025 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,151 +0,0 @@ -/* -** server.c -- a stream socket server demo -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PORT "3490" // the port users will be connecting to - -#define BACKLOG 10 // how many pending connections queue will hold - -void sigchld_handler(int s) -{ - (void)s; // quiet unused variable warning - - // waitpid() might overwrite errno, so we save and restore it: - int saved_errno = errno; - - while(waitpid(-1, NULL, WNOHANG) > 0); - - errno = saved_errno; -} - - -// get sockaddr, IPv4 or IPv6: -void *get_in_addr(struct sockaddr *sa) -{ - if (sa->sa_family == AF_INET) { - return &(((struct sockaddr_in*)sa)->sin_addr); - } - - return &(((struct sockaddr_in6*)sa)->sin6_addr); -} - -int main(void) -{ - // listen on sock_fd, new connection on new_fd - int sockfd, new_fd; - struct addrinfo hints, *servinfo, *p; - struct sockaddr_storage their_addr; // connector's address info - socklen_t sin_size; - struct sigaction sa; - int yes=1; - char s[INET6_ADDRSTRLEN]; - int rv; - - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; // use my IP - - if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) { - fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); - return 1; - } - - // loop through all the results and bind to the first we can - for(p = servinfo; p != NULL; p = p->ai_next) { - if ((sockfd = socket(p->ai_family, p->ai_socktype, - p->ai_protocol)) == -1) { - perror("server: socket"); - continue; - } - - if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, - sizeof(int)) == -1) { - perror("setsockopt"); - exit(1); - } - - if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { - close(sockfd); - perror("server: bind"); - continue; - } - - break; - } - - freeaddrinfo(servinfo); // all done with this structure - - if (p == NULL) { - fprintf(stderr, "server: failed to bind\n"); - exit(1); - } - - if (listen(sockfd, BACKLOG) == -1) { - perror("listen"); - exit(1); - } - - sa.sa_handler = sigchld_handler; // reap all dead processes - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - if (sigaction(SIGCHLD, &sa, NULL) == -1) { - perror("sigaction"); - exit(1); - } - - printf("server: waiting for connections...\n"); - - while(1) { // main accept() loop - sin_size = sizeof their_addr; - new_fd = accept(sockfd, (struct sockaddr *)&their_addr, - &sin_size); - if (new_fd == -1) { - perror("accept"); - continue; - } - - inet_ntop(their_addr.ss_family, - get_in_addr((struct sockaddr *)&their_addr), - s, sizeof s); - printf("server: got connection from %s\n", s); - - char *yo = "HTTP/1.1 OK %s\r\n" - "Content-Type: text/txt\r\n" - "Content-Length: 13\r\n" - "Connection: close\r\n" - "\r\n" - "Hello, world!"; - - if (!fork()) { // this is the child process - close(sockfd); // child doesn't need the listener - if (send(new_fd, - "HTTP/1.1 OK %s\r\n" - "Content-Type: text/txt\r\n" - "Content-Length: 13\r\n" - "Connection: close\r\n" - "\r\n" - "Hello, world!" - , strlen(yo), 0) == -1) - perror("send"); - close(new_fd); - exit(0); - } - close(new_fd); // parent doesn't need this - } - - return 0; -} diff -r 1e61008b9980 -r 114cad94008f seobeo/main.c --- a/seobeo/main.c Mon Sep 29 16:00:44 2025 -0700 +++ b/seobeo/main.c Mon Sep 29 17:00:38 2025 -0700 @@ -7,5 +7,5 @@ int main(void) { - Seobeo_Web_StartBasicHTTPServer("seobeo/pages", "8080"); + Seobeo_Web_StartBasicHTTPServer("seobeo/pages", "8080", SEOBEO_MODE_FORK, 2); } diff -r 1e61008b9980 -r 114cad94008f seobeo/os/s_linux_edge.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/os/s_linux_edge.c Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,66 @@ +#include +#include "seobeo/seobeo.h" + + +void *Seobeo_Web_Edge_Worker(void *vargs) +{ + WorkerArgs *args = vargs; + struct epoll_event events[64]; + while (1) { + int n = epoll_wait(args->evfd, events, 64, -1); + if (n < 0) continue; + for (int i = 0; i < n; i++) { + Seobeo_PHandle h = events[i].data.ptr; + if (h == args->srv) { + // new connection + Seobeo_PHandle cli = + Seobeo_Stream_Handle_Accept(args->srv); + if (!cli) continue; + struct epoll_event ev = { + .events = EPOLLIN, + .data.ptr = cli + }; + epoll_ctl(args->evfd, EPOLL_CTL_ADD, + cli->socket, &ev); + } else { + // client ready + Seobeo_Web_HandleClientRequest(h, args->cache); + epoll_ctl(args->evfd, EPOLL_CTL_DEL, + h->socket, NULL); + Seobeo_Handle_Destroy(h); + } + } + } + return NULL; +} + +void Seobeo_Web_Edge( + Seobeo_PHandle p_server_handle, + int thread_count, + Dowa_PHashMap p_html_cache) +{ + int epfd = epoll_create1(0); + struct epoll_event ev = { + .events = EPOLLIN, + .data.ptr = p_server_handle + }; + epoll_ctl(epfd, EPOLL_CTL_ADD, + p_server_handle->socket, &ev); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 100 * 1024 * 1024); // 100 MB + + pthread_t threads[thread_count]; + WorkerArgs args = { p_server_handle, p_html_cache, epfd }; + for (int i = 0; i < thread_count; i++) + { + pthread_create(&threads[i], NULL, + Seobeo_Web_Edge_Worker, &args); + } + for (int i = 0; i < thread_count; i++) + { + pthread_join(threads[i], NULL); + } + return; +} diff -r 1e61008b9980 -r 114cad94008f seobeo/os/s_macos_edge.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/os/s_macos_edge.c Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,70 @@ +#include +#include "seobeo/seobeo.h" + + +void *Seobeo_Web_Edge_Worker(void *vargs) +{ + WorkerArgs *args = vargs; + struct kevent evlist[64]; + while (1) { + int ne = kevent(args->evfd, NULL, 0, evlist, 64, NULL); + if (ne < 0) continue; + for (int i = 0; i < ne; i++) { + Seobeo_PHandle h = evlist[i].udata; + if (h == args->srv) { + Seobeo_PHandle cli = + Seobeo_Stream_Handle_Accept(args->srv); + if (!cli) continue; + struct kevent kev = { + .ident = cli->socket, + .filter = EVFILT_READ, + .flags = EV_ADD, + .udata = cli + }; + kevent(args->evfd, &kev, 1, NULL, 0, NULL); + } else { + Seobeo_Web_HandleClientRequest(h, args->cache); + struct kevent kev = { + .ident = h->socket, + .filter = EVFILT_READ, + .flags = EV_DELETE, + }; + kevent(args->evfd, &kev, 1, NULL, 0, NULL); + Seobeo_Handle_Destroy(h); + } + } + } + return NULL; +} + +void Seobeo_Web_Edge( + Seobeo_PHandle p_server_handle, + int thread_count, + Dowa_PHashMap p_html_cache) +{ + int kq = kqueue(); + struct kevent kev = { + .ident = p_server_handle->socket, + .filter = EVFILT_READ, + .flags = EV_ADD, + .udata = p_server_handle + }; + kevent(kq, &kev, 1, NULL, 0, NULL); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 100 * 1024 * 1024); // 100 MB + + pthread_t threads[thread_count]; + WorkerArgs args = { p_server_handle, p_html_cache, kq }; + for (int i = 0; i < thread_count; i++) + { + pthread_create(&threads[i], NULL, + Seobeo_Web_Edge_Worker, &args); + } + for (int i = 0; i < thread_count; i++) + { + pthread_join(threads[i], NULL); + } + return; +} diff -r 1e61008b9980 -r 114cad94008f seobeo/pages/hello/bar.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/pages/hello/bar.txt Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,1 @@ +hello diff -r 1e61008b9980 -r 114cad94008f seobeo/pages/hello/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/pages/hello/index.html Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,8 @@ + + + + + +

Hello

+ + diff -r 1e61008b9980 -r 114cad94008f seobeo/pages/hello/index.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/pages/hello/index.js Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,1 @@ +console.log("Hello 2"); diff -r 1e61008b9980 -r 114cad94008f seobeo/pages/index.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/pages/index.js Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,1 @@ +console.log("hello"); diff -r 1e61008b9980 -r 114cad94008f seobeo/s_web.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/seobeo/s_web.c Mon Sep 29 17:00:38 2025 -0700 @@ -0,0 +1,304 @@ +#include "seobeo/seobeo.h" + +void Seobeo_Web_GenerateResponseHeader(void *buffer, int status, + const char *content_type, const int content_length) +{ + const char *status_text; + switch(status) + { + case HTTP_OK: status_text = "OK"; break; + case HTTP_CREATED: status_text = "Created"; break; + case HTTP_MOVED_PERMANENTLY: status_text = "Moved Permanently"; break; + case HTTP_FOUND: status_text = "Found"; break; + case HTTP_BAD_REQUEST: status_text = "Bad Request"; break; + case HTTP_UNAUTHORIZED: status_text = "Unauthorized"; break; + case HTTP_FORBIDDEN: status_text = "Forbidden"; break; + case HTTP_NOT_FOUND: status_text = "Not Found"; break; + case HTTP_INTERNAL_ERROR: status_text = "Internal Server Error"; break; + default: status_text = "Unknown"; break; + } + + sprintf( + buffer, + "HTTP/2.2 %d %s\r\n" + "Content-Type: %s\r\n" + "Content-Length: %d\r\n" + "Connection: close\r\n" + "\r\n", + status, status_text, content_type, content_length + ); +} + +void Seobeo_Web_HandleClientRequest(Seobeo_PHandle p_cli_handle, + Dowa_PHashMap p_html_cache) +{ + Dowa_PArena p_response_arena = Dowa_Arena_Create(8192); + Dowa_PHashMap p_req_map = NULL; + + Dowa_PHashEntry entry = NULL; + Dowa_PHashMap p_current = p_html_cache; + char *slash; + + if (!p_response_arena) { perror("Dowa_Arena_Initialize"); goto clean_up; } + + void *p_response_header = Dowa_Arena_Allocate(p_response_arena, (size_t)2048); + if (!p_response_header) { perror("Dowa_Arena_Allocate"); goto clean_up; } + + p_req_map = Dowa_HashMap_Create(32); + if (Seobeo_Web_ParseClientHeader(p_cli_handle, p_req_map) != 0) + { + // malformed request or closed — respond 400 + Seobeo_Web_GenerateResponseHeader(p_response_header, + HTTP_BAD_REQUEST, + "text/plain", 0); + Seobeo_Handle_Queue(p_cli_handle, + (const uint8*)p_response_header, + (uint32)strlen(p_response_header)); + Seobeo_Handle_Flush(p_cli_handle); + goto clean_up; + } + + const char *path = (const char*)Dowa_HashMap_Get(p_req_map, "Path"); + + char *file_path = Dowa_Arena_Allocate(p_response_arena, (size_t)512); + + if (!path || strcmp(path, "/") == 0) + { + strcpy(file_path, "index.html"); + } + else + { + size_t L = strlen(path); + // strip leading '/' + if (path[0] == '/') + { + if (strchr(path, '.') == NULL) + snprintf(file_path, 512, "%.*s/index.html", (int)(L-1), path+1); + else + snprintf(file_path, 512, "%.*s", (int)(L-1), path+1); + } + else + { + // Probably never get here? + strcpy(file_path, path); + } + } + + // printf("\n\nfile_path: %s\n", file_path); + + // Recursively go though the path until it gets to a file + while ((slash = strchr(file_path, '/'))) + { + *slash = '\0'; // e.g. file_path="foo", slash+1="index.html" + char *dir = file_path; // "foo" + file_path = slash + 1; // "index.html" + + p_current = Dowa_HashMap_Get(p_current, dir); + if (!p_current) { perror("No value"); goto clean_up; } + } + + size_t pos = Dowa_HashMap_GetPosition(p_current, file_path); + entry = p_current->entries[pos]; + + // Missing so 404 + if (!entry) + { + Seobeo_Web_GenerateResponseHeader(p_response_header, + HTTP_NOT_FOUND, + "text/html", 0); + Seobeo_Handle_Queue(p_cli_handle, + (const uint8*)p_response_header, + (uint32)strlen(p_response_header)); + Seobeo_Handle_Flush(p_cli_handle); + goto clean_up; + } + + + const char *mime = "application/octet-stream"; // Default binary + if (strstr(file_path, ".html")) mime = "text/html; charset=utf-8"; + else if (strstr(file_path, ".css")) mime = "text/css"; + else if (strstr(file_path, ".js")) mime = "application/javascript"; + else if (strstr(file_path, ".png")) mime = "image/png"; + else if (strstr(file_path, ".jpg") || strstr(file_path, ".jpeg")) mime = "image/jpeg"; + else if (strstr(file_path, ".gif")) mime = "image/gif"; + else if (strstr(file_path, ".svg")) mime = "image/svg+xml"; + else if (strstr(file_path, ".ico")) mime = "image/x-icon"; + else if (strstr(file_path, ".json")) mime = "application/json"; + + size_t body_size = entry->capacity; + Seobeo_Web_GenerateResponseHeader(p_response_header, + HTTP_OK, + mime, + body_size); + Seobeo_Handle_Queue(p_cli_handle, + (const uint8*)p_response_header, + (uint32)strlen(p_response_header)); + Seobeo_Handle_Queue(p_cli_handle, + (const uint8*)entry->buffer, + (uint32)body_size); + Seobeo_Handle_Flush(p_cli_handle); + +clean_up: + Seobeo_Handle_Destroy(p_cli_handle); + Dowa_Arena_Free(p_response_arena); + Dowa_HashMap_Free(p_req_map); // TODO: Maybe initilized hashmap within the Arena? +} + +int Seobeo_Web_ParseClientHeader(Seobeo_PHandle p_handle, Dowa_PHashMap map) +{ + // 1) Fill read_buffer until we see "\r\n\r\n" + while (1) + { + int r = Seobeo_Handle_Read(p_handle); + if (r < 0) return -1; // fatal error + if (r == -2) return -2; // connection closed TODO: Add this as part of Handle struct. + + if (p_handle->read_buffer_len >= 4 && + strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL) + { + break; + } + if (r == 0) return 1; // EAGAIN, try again later TODO: Add this as part of Handle struct. + } + + // 2) Parse request‐line "METHOD SP PATH SP VERSION CRLF" + char *buf = (char*)p_handle->read_buffer; + char *hdr_end = strstr(buf, "\r\n\r\n"); + size_t hdr_len = hdr_end - buf + 4; + + char method[16], path[256], version[16]; + if (sscanf(buf, "%15s %255s %15s", method, path, version) != 3) + { + return -1; + } + + Dowa_HashMap_PushValueWithType(map, "Method", method, strlen(method) + 1, DOWA_HASH_MAP_TYPE_STRING); + Dowa_HashMap_PushValueWithType(map, "Path", path, strlen(path) + 1, DOWA_HASH_MAP_TYPE_STRING); + Dowa_HashMap_PushValueWithType(map, "Version", version, strlen(version) + 1, DOWA_HASH_MAP_TYPE_STRING); + + // 3) Parse each header line until the blank line + char *line = buf + strlen(method) + 1 + strlen(path) + 1 + strlen(version) + 2; + while (line < hdr_end) + { + char *next = strstr(line, "\r\n"); + if (!next) break; + + // split at colon + char *colon = memchr(line, ':', next - line); + if (colon) { + size_t key_len = colon - line; + size_t value_len = next - colon - 1; + + char *val_start = colon + 1; + if (*val_start == ' ') + { + val_start++; + value_len--; + } + + char *key = malloc(key_len + 1); + memcpy(key, line, key_len); + key[key_len] = '\0'; + + char *val = malloc(value_len + 1); + memcpy(val, val_start, value_len); + val[value_len] = '\0'; + + Dowa_HashMap_PushValue(map, key, val, value_len + 1); + + free(key); + free(val); + } + + line = next + 2; + } + Seobeo_Handle_Consume(p_handle, (uint32)hdr_len); + + // 4) If Content-Length was provided, read that much body + int content_length_pos = Dowa_HashMap_GetPosition(map, "Content-Length"); + Dowa_PHashEntry p_content_length_entry = map->entries[content_length_pos]; + if (p_content_length_entry) + { + size_t body_len = atoi((char*)p_content_length_entry->buffer); + while (p_handle->read_buffer_len < body_len) + { + int r = Seobeo_Handle_Read(p_handle); + if (r < 0) return -1; + if (r == 0) return 1; // wait for more data + } + + char *body = malloc(body_len + 1); + memcpy(body, p_handle->read_buffer, body_len); + body[body_len] = '\0'; + + Dowa_HashMap_PushValue(map, "Body", body, body_len + 1); + free(body); + + Seobeo_Handle_Consume(p_handle, (uint32)body_len); + } + + return 0; // success; map now holds Method, Path, Version, headers, and optional Body +} + +// TODO: Do epoll or kqueue depending on the OS. +void SigchildHandler(int s) +{ + (void)s; // quiet unused variable warning + + // waitpid() might overwrite errno, so we save and restore it: + int saved_errno = errno; + + while(waitpid(-1, NULL, WNOHANG) > 0); + + errno = saved_errno; +} + +int Seobeo_Web_StartBasicHTTPServer( + const char *folder_path, + const char *port, + Seobeo_ServerMode mode, + int thread_count) +{ + Dowa_PHashMap p_html_cache = Dowa_HashMap_Create(1024); + if (Dowa_HashMap_Cache_Folder(p_html_cache, + folder_path) != 0) + { + perror("Dowa_Cache_Folder"); + return -1; + } + + Seobeo_PHandle p_server_handle = + Seobeo_Stream_Handle_Create(NULL, port); + if (p_server_handle->socket < 0) return 1; + printf("Listening on port %s\n", port); + + // Fork‐based fallback + if (mode == SEOBEO_MODE_FORK) + { + struct sigaction sa; + sa.sa_handler = SigchildHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGCHLD, &sa, NULL); + + while (1) { + Seobeo_PHandle cli = + Seobeo_Stream_Handle_Accept(p_server_handle); + if (!cli) continue; + + if (fork() == 0) { + Seobeo_Web_HandleClientRequest(cli, + p_html_cache); + _exit(0); + } + Seobeo_Handle_Destroy(cli); + } + } + + if (mode == SEOBEO_MODE_EDGE) + { + Seobeo_Web_Edge(p_server_handle, thread_count, p_html_cache); + } + + return -1; +} diff -r 1e61008b9980 -r 114cad94008f seobeo/seobeo.h --- a/seobeo/seobeo.h Mon Sep 29 16:00:44 2025 -0700 +++ b/seobeo/seobeo.h Mon Sep 29 17:00:38 2025 -0700 @@ -18,6 +18,7 @@ #include #include #include +#include #include "dowa/dowa.h" @@ -53,6 +54,16 @@ char *file_name; } Sebeo_Handle, *Seobeo_PHandle; +typedef struct { + Seobeo_PHandle srv; + Dowa_PHashMap cache; + int evfd; // epoll‐fd or kqueue‐fd +} WorkerArgs; + +typedef enum { + SEOBEO_MODE_FORK, + SEOBEO_MODE_EDGE, +} Seobeo_ServerMode; // --- Socket, IP related --- // extern int Seobeo_CreateSocket(int32 stream, const char *host, const char* port, int32 backlog); @@ -67,7 +78,9 @@ extern void Seobeo_Web_GenerateResponseHeader(void *buffer, int status, const char *content_type, const int content_length); extern void Seobeo_Web_HandleClientRequest(Seobeo_PHandle cli, Dowa_PHashMap p_html_cache); extern int Seobeo_Web_ParseClientHeader(Seobeo_PHandle p_handle, Dowa_PHashMap map); -extern int Seobeo_Web_StartBasicHTTPServer(const char *folder_path, const char *port); +extern int Seobeo_Web_StartBasicHTTPServer(const char *folder_path, const char *port, Seobeo_ServerMode mode, int thread_count); +extern void *Seobeo_Web_Edge_Worker(void *vargs); // Maybe not web only... +extern void Seobeo_Web_Edge(Seobeo_PHandle p_server_handle, int thread_count, Dowa_PHashMap p_html_cache); // --- Helper functions --- // extern void Seobeo_Handle_Destroy(Seobeo_PHandle p_handle);