# How the WebServer Serves the Preferences UI at localhost:62718 in HallelujahIM

> Discover how the WebServer serves the preferences UI at localhost:62718 by binding GCDWebServer, serving Vue.js files, and exposing a JSON API for NSUserDefaults. Learn the technical details.

- Repository: [dongyuwei/hallelujahim](https://github.com/dongyuwei/hallelujahim)
- Tags: internals
- Published: 2026-02-28

---

**The `WebServer` class wraps GCDWebServer to bind a local HTTP server to port 62718, serves static Vue.js files from the app bundle's `web` directory, and exposes a JSON API at `/preference` for reading and writing `NSUserDefaults`.**

The dongyuwei/hallelujahim repository implements a lightweight preferences interface using an embedded web server. By navigating to `http://localhost:62718/`, users interact with a Vue.js application that communicates with the backend via a RESTful API. This architecture allows the input method editor to expose configuration options through a browser-based UI without external dependencies.

## Starting the Local HTTP Server on Port 62718

The server initialization happens in `src/WebServer.m` using the **GCDWebServer** framework. The implementation hardcodes port `62718` and restricts binding to localhost only, preventing external network access.

```objc
static int port = 62718;                               // src/WebServer.m L20
options[GCDWebServerOption_Port] = @(port);            // src/WebServer.m L71
options[GCDWebServerOption_BindToLocalhost] = @YES;   // src/WebServer.m L72
[webServer startWithOptions:options error:nil];

```

This configuration ensures the preferences UI is accessible only from the local machine at `http://localhost:62718/`.

## Serving the Static Preferences UI

Once the server starts, it registers a handler to serve static files from the `web` directory inside the app bundle. This directory contains [`index.html`](https://github.com/dongyuwei/hallelujahim/blob/main/index.html), [`index.js`](https://github.com/dongyuwei/hallelujahim/blob/main/index.js), [`vue.js`](https://github.com/dongyuwei/hallelujahim/blob/main/vue.js), and [`index.css`](https://github.com/dongyuwei/hallelujahim/blob/main/index.css).

```objc
[webServer addGETHandlerForBasePath:@"/"
                      directoryPath:[NSString stringWithFormat:@"%@/%@", [NSBundle mainBundle].resourcePath, @"web"]
                      indexFilename:nil
                           cacheAge:3600
                 allowRangeRequests:YES];            // src/WebServer.m L36-L42

```

A request to `http://localhost:62718/` returns [`index.html`](https://github.com/dongyuwei/hallelujahim/blob/main/index.html), which bootstraps the Vue.js single-page application.

## Exposing the /preference API Endpoint

The server exposes a JSON API at `/preference` for reading and updating user settings stored in `NSUserDefaults`. The implementation handles both GET and POST methods in `src/WebServer.m`.

### Reading Preferences (GET)

The GET handler returns the current boolean values for `TRANSLATION_KEY` and `COMMIT_WORD_WITH_SPACE_KEY`:

```objc
[webServer addHandlerForMethod:@"GET"
                          path:@"/preference"
                  requestClass:[GCDWebServerRequest class]
                  processBlock:^GCDWebServerResponse *(GCDWebServerRequest *request) {
    return [GCDWebServerDataResponse responseWithJSONObject:@{
        TRANSLATION_KEY : @([preference boolForKey:TRANSLATION_KEY]),
        COMMIT_WORD_WITH_SPACE_KEY : @([preference boolForKey:COMMIT_WORD_WITH_SPACE_KEY])
    }];
}];                                                   // src/WebServer.m L43-L53

```

### Updating Preferences (POST)

The POST handler receives a JSON body, writes the new values to `NSUserDefaults`, and echoes the payload:

```objc
[webServer addHandlerForMethod:@"POST"
                          path:@"/preference"
                  requestClass:[GCDWebServerURLEncodedFormRequest class]
                  processBlock:^GCDWebServerResponse *(GCDWebServerRequest *request) {
    NSDictionary *data = ((GCDWebServerDataRequest *)request).jsonObject;
    bool showTranslation = [data[TRANSLATION_KEY] boolValue];
    [preference setBool:showTranslation forKey:TRANSLATION_KEY];
    bool commitWordWithSpace = [data[COMMIT_WORD_WITH_SPACE_KEY] boolValue];
    [preference setBool:commitWordWithSpace forKey:COMMIT_WORD_WITH_SPACE_KEY];
    return [GCDWebServerDataResponse responseWithJSONObject:data];
}];                                                   // src/WebServer.m L55-L68

```

## Frontend Integration with Vue.js

The **Vue.js** frontend in [`web/index.html`](https://github.com/dongyuwei/hallelujahim/blob/main/web/index.html) and [`web/index.js`](https://github.com/dongyuwei/hallelujahim/blob/main/web/index.js) communicates with the `/preference` endpoint. On page load, it fetches current settings:

```javascript
fetch("http://localhost:62718/preference")          // get current settings
  .then(r => r.json())
  .then(pref => { this.preference = pref; });

```

When the user submits the form, the app POSTs the updated values back to the server:

```javascript
fetch("http://localhost:62718/preference", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(this.preference)
})
.then(r => r.json())
.then(updated => { console.log("Saved:", updated); });

```

This creates a seamless loop where the browser-based UI reads from and writes to the application's `NSUserDefaults` via the local HTTP server.

## Summary

- **GCDWebServer** powers the `WebServer` singleton, binding to `localhost:62718` exclusively to prevent external access.
- **Static file serving** from the `web/` directory delivers the Vue.js application at the root path `/`.
- **RESTful API** at `/preference` handles GET requests to read `NSUserDefaults` and POST requests to update boolean settings for translation and commit-word-with-space features.
- **Vue.js frontend** in [`web/index.js`](https://github.com/dongyuwei/hallelujahim/blob/main/web/index.js) consumes the API, providing a reactive interface for configuring the input method editor.

## Frequently Asked Questions

### Why does the WebServer bind to localhost only?

The server sets `GCDWebServerOption_BindToLocalhost` to `YES` in `src/WebServer.m` line 72. This security measure ensures the preferences UI is accessible only from the local machine, preventing remote devices from accessing or modifying application settings through the HTTP interface.

### What happens if port 62718 is already in use?

The analysis does not show explicit error handling for port conflicts in the provided code. Since the port is hardcoded to `62718` in `src/WebServer.m` line 20, the server would fail to start if another process occupies that port, likely returning an error through the `startWithOptions:error:` method's error pointer.

### How does the frontend know when preferences are saved?

The POST handler in `src/WebServer.m` lines 55-68 returns the updated JSON payload immediately after writing to `NSUserDefaults`. The Vue.js frontend in [`web/index.js`](https://github.com/dongyuwei/hallelujahim/blob/main/web/index.js) receives this response and logs the confirmation, providing immediate feedback that the settings have been persisted to the application's user defaults.

### Can I access the preferences UI from another device on the network?

No. The server explicitly binds to localhost only via `GCDWebServerOption_BindToLocalhost` set to `YES` in `src/WebServer.m` line 72. This restriction prevents network access, ensuring only the local user can view and modify preferences through the browser interface at `http://localhost:62718/`.