Changelog
View Source0.4.0
Breaking changes
- WebSocket process messages go through the handler. Messages sent with
send(socket.transport, msg)are now delivered to the handler as{:info, msg}first. Without an{:info, _}clause they are forwarded to the client as before (binaries, maps, lists and{type, payload}frames); any other message is logged and dropped instead of crashing the socket. A catch-all clause such as_event, _socket -> :oknow also catches these messages, so they are no longer forwarded. Add an explicit{:info, msg}, _socket -> {:reply, msg}clause to keep forwarding them. - Custom error handlers receive the exception. Errors raised before a route
runs (for example
Plug.Parsers.ParseErrorfor malformed JSON) used to reach the handler as%{kind: _, reason: _, stack: _}. The handler now always gets{:error, reason}, an exception, or{kind, reason}for throws and exits. - The default error page uses the exception's status. Malformed JSON
returns 400 instead of 500, and exceptions with a
plug_status(such asEcto.NoResultsError) return that status.{:error, _}still returns 500. static,parserandlog_levelare compile-time configuration. They are read withApplication.compile_env, so changing them recompiles the router, and setting them only inconfig/runtime.exsfails at boot instead of being silently ignored.- The
PORTenvironment variable is used whenbandit_optshas no:port. An invalid value fails at boot with an error namingPORT. - Internal modules changed.
ws/3andsse/3no longer generate a module per route; the public helpers ofFrancis.Websocket(setup_heartbeat/1,handle_heartbeat/1,cancel_heartbeat/1,call_join/2,call_close/3) were removed;Francis.SSE.run/3now takes(conn, handler, opts); andFrancis.SSE.format_event/1returns iodata. These modules were documented as internal. - SSE output is sanitised. Multi-line data is sent as one
data:line per line, line breaks are stripped fromeventandid, and a non-integerretryis omitted. - Elixir
~> 1.15is required.
Fixed
ws/3routes could replace Francis modules (ws "/static"replacedFrancis.Static), and two routers declaring the same WebSocket path shared one handler.ws/3andsse/3handlers can use the router's aliases, imports and module attributes.- Optional events (
:join,{:close, _},{:info, _}) are skipped when the handler has no clause for them, including handlers that delegate to another function; aFunctionClauseErrorraised deeper in user code is no longer swallowed. - Open redirect:
redirect/2,3sends paths containing a backslash, control characters or leading whitespace (e.g./\evil.com) to/. - SSE event injection through line breaks in relayed messages.
bandit_optsanderror_handlerfrom application config were ignored.static: falsedid not disable static serving.mix francis.digestre-digested its own output when run again, path traversal through manifest entries in--clean, and--agewas ignored.mix francis.new --no-supgenerated a supervised app.mix francis.release --portonly changedEXPOSE; the Dockerfile now also setsENV PORT.- The dev watcher scanned
deps/and_build/every 100ms, evaluatedmix.exsand tests, crashed on a file that failed to compile, and found nothing in umbrella apps.
Added
{:info, msg}WebSocket event.PORTenvironment variable support.Francis.Static.cache_control/1, and the digest manifest is cached in:persistent_term.- Static files are served gzipped when a
.gzvariant exists (gzip: true).
Dependencies
- Updated bandit, plug, thousand_island, websock_adapter, req (test), finch, mint and others to their latest versions.