:- module(test_websocket, [ test_websocket/0 ]). :- asserta(user:file_search_path(foreign, '.')). :- asserta(user:file_search_path(foreign, '../clib')). :- asserta(user:file_search_path(foreign, '../sgml')). :- asserta(user:file_search_path(library, '..')). :- asserta(user:file_search_path(library, '../sgml')). :- asserta(user:file_search_path(library, '../plunit')). :- asserta(user:file_search_path(library, '../clib')). :- use_module(library(plunit)). :- use_module(library(http/websocket)). :- use_module(library(apply)). :- use_module(library(option)). :- use_module(library(debug)). :- use_module(library(lists)). :- use_module(library(http/thread_httpd)). :- use_module(library(http/http_dispatch)). test_websocket :- run_tests([ serialization, http ]). :- begin_tests(serialization). test(text, Reply == [ websocket{opcode:text, format:string, data:"Hello world"} ]) :- ws_loop_close([text("Hello world")], Reply, []). test(unicode, Reply == [ websocket{opcode:text, format:string, data:Data} ]) :- unicode_data(Data), ws_loop_close([text(Data)], Reply, []). test(prolog, Reply == [ websocket{opcode:text, format:prolog, data:hello(world)} ]) :- ws_loop_close([prolog(hello(world))], Reply, [format(prolog)]). test(json, Reply =@= [ websocket{opcode:text, format:json, data:_{hello:world}} ]) :- ws_loop_close([json(_{hello:world})], Reply, [ format(json), value_string_as(atom) ]). test(split, Reply == [ websocket{opcode:text, format:string, data:"0123456789"} ]) :- ws_loop_close([text("0123456789"), close], Reply, [ buffer_size(5) ]). :- end_tests(serialization). :- begin_tests(http). test(echo, Reply == [ websocket{opcode:text, format:string, data:"Hello world"}, websocket{opcode:text, format:string, data:Unicode}, websocket{opcode:close, code:1005, format:string, data:"Ciao"} ]) :- Address = localhost:Port, unicode_data(Unicode), setup_call_cleanup( server(Address), client(Port, [ text("Hello world"), text(Unicode), close(1005, "Ciao") ], Reply), http_stop_server(Port, [])). :- end_tests(http). unicode_data( "\u0420\u0443\u0441\u0441\u043A\u0438\u0439 \u044F\u0437\u044B\u043A"). /******************************* * SERIALIZATION SUPPORT * *******************************/ ws_loop_close(Messages, Result, Options) :- append(Messages, [close], Messages1), ws_loop(Messages1, Result0, Options), once(append(Result, [Close], Result0)), assertion(Close == websocket{opcode:close, format:string, code:1000, data:""}). ws_loop(Messages, Result, Options) :- is_list(Messages), !, setup_call_cleanup( tmp_file(ws, File), ( ws_write_file(File, Messages, Options), ws_read_file(File, Result, Options) ), delete_file(File)). ws_loop(Message, Result, Options) :- ws_loop([Message], Result, Options). ws_write_file(File, Messages, Options) :- option(close_parent(true), Options, true), !, open(File, write, Out, [type(binary)]), ws_write_stream(Out, Messages, Options). ws_write_file(File, Messages, Options) :- setup_call_cleanup( open(File, write, Out, [type(binary)]), ws_write_stream(Out, Messages, Options), close(Out)). ws_write_stream(Stream, Messages, Options) :- setup_call_cleanup( ws_open(Stream, WsStream, Options), maplist(ws_send(WsStream), Messages), close(WsStream)). /******************************* * READ * *******************************/ ws_read_file(File, Message, Options) :- setup_call_cleanup( open(File, read, In, [type(binary)]), ws_read_stream(In, Message, Options), close(In)). ws_read_stream(Stream, Messages, Options) :- setup_call_cleanup( ws_open(Stream, WsStream, [close_parent(false)]), ws_receive_all(WsStream, Messages, Options), close(WsStream)). ws_receive_all(WsStream, Messages, Options) :- ws_receive(WsStream, H, Options), ( H == end_of_file -> Messages = [] ; Messages = [H|T], ( H.opcode == close -> T = [] ; ws_receive_all(WsStream, T, Options) ) ). /******************************* * HTTP * *******************************/ :- http_handler(root(echo), http_upgrade_to_websocket(echo, [ subprotocols([echo]) ]), [spawn([])]). server(Port) :- http_server(http_dispatch, [port(Port)]). echo(WebSocket) :- ws_receive(WebSocket, Message), debug(websocket, 'Got ~p', [Message]), ws_send(WebSocket, Message), ( Message.opcode == close -> true ; echo(WebSocket) ). client(Port, Messages, Reply) :- format(string(URL), 'ws://localhost:~d/echo', [Port]), http_open_websocket(URL, WebSocket, []), maplist(ws_send(WebSocket), Messages), ws_receive_all(WebSocket, Reply, []), ws_close(WebSocket, 1000, "bye").