| 1764 | /* ── Public API ───────────────────────────────────────────────── */ |
| 1765 | |
| 1766 | cbm_http_server_t *cbm_http_server_new(int port) { |
| 1767 | cbm_http_server_t *srv = calloc(1, sizeof(*srv)); |
| 1768 | if (!srv) |
| 1769 | return NULL; |
| 1770 | |
| 1771 | srv->port = port; |
| 1772 | atomic_store(&srv->stop_flag, 0); |
| 1773 | |
| 1774 | /* Create a dedicated MCP server for HTTP (own SQLite connection) */ |
| 1775 | srv->mcp = cbm_mcp_server_new(NULL); |
| 1776 | if (!srv->mcp) { |
| 1777 | cbm_log_error("ui.http.mcp_fail", "reason", "cannot create MCP instance"); |
| 1778 | free(srv); |
| 1779 | return NULL; |
| 1780 | } |
| 1781 | |
| 1782 | /* Bind to localhost only (httpd refuses anything else by construction) */ |
| 1783 | srv->listener = cbm_httpd_listen(port); |
| 1784 | if (!srv->listener) { |
| 1785 | char port_str[16]; |
| 1786 | snprintf(port_str, sizeof(port_str), "%d", port); |
| 1787 | cbm_log_warn("ui.unavailable", "port", port_str, "reason", "in_use", "hint", |
| 1788 | "use --port=N to override"); |
| 1789 | cbm_mcp_server_free(srv->mcp); |
| 1790 | free(srv); |
| 1791 | return NULL; |
| 1792 | } |
| 1793 | |
| 1794 | srv->port = cbm_httpd_port(srv->listener); |
| 1795 | srv->listener_ok = true; |
| 1796 | |
| 1797 | char port_str[16]; |
| 1798 | snprintf(port_str, sizeof(port_str), "%d", srv->port); |
| 1799 | char url[64]; |
| 1800 | snprintf(url, sizeof(url), "http://127.0.0.1:%d", srv->port); |
| 1801 | cbm_log_info("ui.serving", "url", url, "port", port_str); |
| 1802 | |
| 1803 | return srv; |
| 1804 | } |
| 1805 | |
| 1806 | void cbm_http_server_free(cbm_http_server_t *srv) { |
| 1807 | if (!srv) |