2
0

WebHTTPServer.Mod 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. MODULE WebHTTPServer; (** AUTHOR "pjm/tf/be"; PURPOSE "HTTP/1.1 Server";*)
  2. IMPORT
  3. KernelLog, Machine, Kernel, Objects, WebHTTP, AosLog := TFLog, Modules, Streams, Files,
  4. IP, TCP, TCPServices, Classes := TFClasses, Clock, Dates, Strings;
  5. CONST
  6. Ok* = TCPServices.Ok;
  7. Error* = -1;
  8. Major* = 1; Minor* = 1;
  9. FileBufSize = 4096;
  10. ServerVersion* = "A2 HTTP Server/1.0";
  11. DocType* = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">';
  12. Tab = 09X;
  13. Timeout = 300*1000; (* [ms] timeout for keep-alive *)
  14. MaxErrors = 10;
  15. Log = FALSE;
  16. TYPE
  17. Name* = ARRAY 64 OF CHAR;
  18. (** abstract HTTP plugin *)
  19. HTTPPlugin* = OBJECT
  20. VAR
  21. name*: Name;
  22. PROCEDURE &Init*(CONST name: Name);
  23. BEGIN COPY(name, SELF.name)
  24. END Init;
  25. (** if CanHandle returns TRUE, the Handler procedure will be called *)
  26. PROCEDURE CanHandle* (host: Host; VAR header : WebHTTP.RequestHeader; secure: BOOLEAN) : BOOLEAN;
  27. BEGIN HALT(301);
  28. RETURN FALSE
  29. END CanHandle;
  30. (** default LocateResource method *)
  31. PROCEDURE LocateResource*(host: Host; VAR header: WebHTTP.RequestHeader; VAR reply: WebHTTP.ResponseHeader; VAR f: Files.File);
  32. VAR
  33. name, ext: Files.FileName; i, d, t: LONGINT; modsince: ARRAY 32 OF CHAR;
  34. path : ARRAY 1024 OF CHAR;
  35. PROCEDURE Add(CONST s: ARRAY OF CHAR);
  36. VAR j, k: LONGINT; ch: CHAR;
  37. BEGIN
  38. j := 0; k := 0;
  39. LOOP
  40. IF i = LEN(name) THEN reply.statuscode := WebHTTP.RequestURITooLong; EXIT END;
  41. ch := s[j];
  42. IF ch = "." THEN k := 0 END;
  43. name[i] := ch; ext[k] := ch;
  44. IF ch = 0X THEN EXIT END;
  45. INC(i); INC(j); INC(k)
  46. END;
  47. END Add;
  48. BEGIN
  49. i := 0; reply.statuscode := WebHTTP.OK;
  50. Add(host.prefix); WebHTTP.GetPath(header.uri, path); Add(path);
  51. IF (reply.statuscode = WebHTTP.OK) THEN
  52. f := Files.Old(name);
  53. IF (f # NIL) & (Files.Directory IN f.flags) THEN (* do not send directory offals *)
  54. Strings.Concat("http://", header.host, reply.location);
  55. Strings.Append(reply.location, header.uri);
  56. Strings.Append(reply.location, "/");
  57. reply.statuscode := WebHTTP.ObjectMoved
  58. ELSE
  59. IF (name[i-1] = "/") THEN
  60. Strings.Concat("http://", header.host, reply.contentlocation);
  61. Strings.Append(reply.contentlocation, header.uri);
  62. Strings.Append(reply.contentlocation, host.default);
  63. Add(host.default)
  64. END;
  65. IF (reply.statuscode = WebHTTP.OK) THEN
  66. f := Files.Old(name);
  67. IF f # NIL THEN
  68. f.GetDate(t, d);
  69. Strings.FormatDateTime(WebHTTP.DateTimeFormat, Dates.OberonToDateTime(d, t), reply.lastmodified);
  70. IF WebHTTP.GetAdditionalFieldValue(header.additionalFields, "If-Modified-Since", modsince) &
  71. (modsince = reply.lastmodified)
  72. THEN
  73. reply.statuscode := WebHTTP.NotModified;
  74. f := NIL
  75. ELSE
  76. (* TODO: move to Configuration.XML / separate plugins *)
  77. IF ext = ".html" THEN COPY("text/html; charset=utf-8", reply.contenttype)
  78. ELSIF ext = ".txt" THEN COPY("text/plain", reply.contenttype)
  79. ELSIF ext = ".css" THEN COPY("text/css", reply.contenttype)
  80. ELSIF ext = ".gif" THEN COPY("image/gif", reply.contenttype)
  81. ELSIF ext = ".jpg" THEN COPY("image/jpeg", reply.contenttype)
  82. ELSIF ext = ".pdf" THEN COPY("application/pdf", reply.contenttype)
  83. ELSIF ext = ".xsl" THEN COPY("text/xsl", reply.contenttype)
  84. ELSIF ext = ".xml" THEN COPY("text/xml", reply.contenttype)
  85. ELSE COPY("application/octet-stream", reply.contenttype)
  86. END
  87. END
  88. ELSE
  89. reply.statuscode := WebHTTP.NotFound; COPY("text/html", reply.contenttype);
  90. f := Files.Old(host.error);
  91. END
  92. END
  93. END
  94. END
  95. END LocateResource;
  96. (* handles a HTTP request *)
  97. PROCEDURE Handle*(host: Host; VAR request: WebHTTP.RequestHeader; VAR reply: WebHTTP.ResponseHeader;
  98. VAR in: Streams.Reader; VAR out: Streams.Writer);
  99. BEGIN HALT(301)
  100. END Handle;
  101. END HTTPPlugin;
  102. (* default plugin for all hosts. Each host has this default plugin *)
  103. DefaultPlugin = OBJECT(HTTPPlugin)
  104. PROCEDURE CanHandle*(host : Host; VAR header: WebHTTP.RequestHeader; secure : BOOLEAN): BOOLEAN;
  105. BEGIN RETURN TRUE
  106. END CanHandle;
  107. PROCEDURE Handle*(host: Host; VAR request: WebHTTP.RequestHeader; VAR reply: WebHTTP.ResponseHeader;
  108. VAR in: Streams.Reader; VAR out: Streams.Writer);
  109. VAR f: Files.File; fr: Files.Reader; c: WebHTTP.ChunkedOutStream; w: Streams.Writer;
  110. BEGIN
  111. IF (request.method IN {WebHTTP.GetM, WebHTTP.HeadM}) THEN
  112. LocateResource(host, request, reply, f);
  113. IF Log THEN
  114. WebHTTP.LogRequestHeader(log, request);
  115. WebHTTP.LogResponseHeader(log, reply)
  116. END;
  117. IF (reply.statuscode = WebHTTP.OK) OR (reply.statuscode = WebHTTP.NotFound) THEN
  118. IF (f # NIL) THEN
  119. reply.contentlength := f.Length();
  120. WebHTTP.SendResponseHeader(reply, out);
  121. IF (request.method = WebHTTP.GetM) THEN
  122. Files.OpenReader(fr, f, 0);
  123. SendData(fr, out)
  124. END
  125. ELSE
  126. reply.statuscode := WebHTTP.NotFound;
  127. (*WebHTTP.SendResponseHeader(reply, out);*)
  128. IF (request.method = WebHTTP.GetM) THEN
  129. NEW(c, w, out, request, reply);
  130. WebHTTP.SendResponseHeader(reply, out);
  131. WebHTTP.WriteHTMLStatus(reply, w);
  132. w.Update;
  133. c.Close
  134. (* *) ELSE WebHTTP.SendResponseHeader(reply, out);
  135. END
  136. END
  137. ELSIF (reply.statuscode = WebHTTP.NotModified) THEN
  138. WebHTTP.SendResponseHeader(reply, out)
  139. ELSIF (reply.statuscode = WebHTTP.ObjectMoved) THEN
  140. (*WebHTTP.SendResponseHeader(reply, out);*)
  141. IF (request.method = WebHTTP.GetM) THEN
  142. NEW(c, w, out, request, reply);
  143. WebHTTP.SendResponseHeader(reply, out);
  144. WebHTTP.WriteHTMLStatus(reply, w);
  145. w.Update;
  146. c.Close
  147. (* *) ELSE WebHTTP.SendResponseHeader(reply, out);
  148. END
  149. END
  150. ELSE
  151. reply.statuscode := WebHTTP.NotImplemented;
  152. WebHTTP.WriteStatus(reply, out)
  153. END
  154. END Handle;
  155. END DefaultPlugin;
  156. Statistics = OBJECT
  157. VAR
  158. bucket : LONGINT;
  159. secondBuckets: ARRAY 60 OF LONGINT;
  160. timer : Kernel.Timer;
  161. avg : LONGINT;
  162. alive : BOOLEAN;
  163. logCounter: LONGINT;
  164. PROCEDURE Hit;
  165. BEGIN {EXCLUSIVE}
  166. INC(secondBuckets[bucket]);
  167. INC(nofRequests)
  168. END Hit;
  169. PROCEDURE Update;
  170. BEGIN {EXCLUSIVE}
  171. avg := avg + secondBuckets[bucket];
  172. bucket := (bucket + 1) MOD 60;
  173. avg := avg - secondBuckets[bucket];
  174. secondBuckets[bucket] := 0;
  175. requestsPerMinute := avg;
  176. logCounter := (logCounter + 1) MOD 40H;
  177. IF (logCounter = 0) THEN
  178. FlushW3CLog
  179. END
  180. END Update;
  181. PROCEDURE Kill;
  182. BEGIN
  183. alive := FALSE;
  184. timer.Wakeup
  185. END Kill;
  186. BEGIN {ACTIVE}
  187. NEW(timer); alive := TRUE;
  188. WHILE alive DO
  189. timer.Sleep(1000);
  190. Update
  191. END;
  192. END Statistics;
  193. HostList* = OBJECT
  194. VAR
  195. host*: Host;
  196. next*: HostList;
  197. END HostList;
  198. Host* = OBJECT
  199. VAR
  200. name-: Name;
  201. plugins : Classes.List;
  202. prefix-, default-, error-: Files.FileName;
  203. PROCEDURE &Init*(CONST name: ARRAY OF CHAR);
  204. BEGIN
  205. COPY(name, SELF.name);
  206. COPY("", prefix);
  207. COPY("index.html", default);
  208. COPY("error.html", error);
  209. NEW(plugins);
  210. (* install default plugin *)
  211. plugins.Add(defaultPlugin);
  212. END Init;
  213. PROCEDURE AddPlugin*(pi : HTTPPlugin);
  214. BEGIN {EXCLUSIVE}
  215. IF plugins.IndexOf(pi) >= 0 THEN KernelLog.String("Plugin already plugged in"); KernelLog.Ln
  216. ELSE
  217. plugins.Add(pi)
  218. END
  219. END AddPlugin;
  220. PROCEDURE RemovePlugin*(pi : HTTPPlugin);
  221. BEGIN {EXCLUSIVE}
  222. plugins.Remove(pi)
  223. END RemovePlugin;
  224. PROCEDURE SetPrefix*(CONST Prefix: ARRAY OF CHAR);
  225. BEGIN {EXCLUSIVE}
  226. COPY(Prefix, prefix)
  227. END SetPrefix;
  228. PROCEDURE SetDefault*(CONST Default: ARRAY OF CHAR);
  229. BEGIN {EXCLUSIVE}
  230. COPY(Default, default)
  231. END SetDefault;
  232. PROCEDURE SetError*(CONST Error: ARRAY OF CHAR);
  233. BEGIN {EXCLUSIVE}
  234. COPY(Error, error)
  235. END SetError;
  236. PROCEDURE Handle*(
  237. VAR request: WebHTTP.RequestHeader; VAR reply: WebHTTP.ResponseHeader;
  238. VAR in: Streams.Reader; VAR out: Streams.Writer; secure : BOOLEAN
  239. );
  240. VAR i: LONGINT; pi: HTTPPlugin; p: ANY; exit: BOOLEAN;
  241. BEGIN
  242. BEGIN {EXCLUSIVE}
  243. exit := FALSE;
  244. i := plugins.GetCount()-1;
  245. WHILE (i >= 0) & (~exit) DO
  246. p := plugins.GetItem(i);
  247. IF p(HTTPPlugin).CanHandle(SELF, request, secure) THEN pi := p(HTTPPlugin); exit := TRUE END;
  248. DEC(i);
  249. END;
  250. END;
  251. IF pi # NIL THEN
  252. IF Log THEN
  253. log.String("request handled by "); log.String(pi.name); log.Ln
  254. END;
  255. pi.Handle(SELF, request, reply, in, out);
  256. ELSE
  257. HALT(99)
  258. END;
  259. END Handle;
  260. END Host;
  261. HTTPAgent = OBJECT (TCPServices.Agent)
  262. VAR
  263. res: WORD;
  264. len : LONGINT;
  265. body, closeRequested: BOOLEAN;
  266. out: Streams.Writer; in, inR: Streams.Reader;
  267. o : ANY;
  268. h, th : Host;
  269. i : LONGINT;
  270. request : WebHTTP.RequestHeader;
  271. reply: WebHTTP.ResponseHeader;
  272. value: ARRAY 128 OF CHAR;
  273. timeout: Objects.Timer;
  274. dechunk: WebHTTP.ChunkedInStream;
  275. consecutiveErrors: LONGINT;
  276. secure : BOOLEAN;
  277. listenerProc : ListenerProc;
  278. PROCEDURE HandleTimeout;
  279. BEGIN client.Close
  280. END HandleTimeout;
  281. BEGIN {ACTIVE}
  282. NEW(timeout);
  283. (* open streams *)
  284. Streams.OpenReader(in, client.Receive);
  285. Streams.OpenWriter(out, client.Send);
  286. Machine.AtomicInc(nofConnects);
  287. (* read request *)
  288. request.fadr := client.fip;
  289. request.fport := client.fport;
  290. consecutiveErrors := 0;
  291. REPEAT
  292. Objects.SetTimeout(timeout, HandleTimeout, Timeout);
  293. WebHTTP.ParseRequest(in, request, res, log);
  294. IF Log THEN WebHTTP.LogRequestHeader(log,request) END;
  295. Objects.CancelTimeout(timeout);
  296. IF (client.state = TCP.Established) THEN
  297. IF (Strings.Pos("hunked", request.transferencoding) > 0) THEN
  298. NEW(dechunk, in, inR)
  299. ELSE
  300. inR := in
  301. END;
  302. (* handle request *)
  303. GetDefaultResponseHeader(request, reply);
  304. len := 0; body := FALSE;
  305. hitStat.Hit;
  306. IF (res = WebHTTP.OK) THEN
  307. i := 0; WHILE (request.host[i] # 0X) & (request.host[i] # ":") DO INC(i) END;
  308. request.host[i] := 0X;
  309. h := defaultHost;
  310. hosts.Lock;
  311. i := hosts.GetCount()-1;
  312. WHILE (i >= 0) DO
  313. o := hosts.GetItem(i); th := o(Host);
  314. IF Strings.Match(th.name, request.host) THEN h := th; i := 0 END;
  315. DEC(i)
  316. END;
  317. hosts.Unlock;
  318. IF Log THEN
  319. log.String(request.uri); log.String(" handled by ");
  320. IF (h.name = "") THEN log.String(" default host")
  321. ELSE log.String(h.name)
  322. END;
  323. log.Ln
  324. END;
  325. h.Handle(request, reply, inR, out, secure);
  326. listenerProc := listener;
  327. IF (listenerProc # NIL) THEN
  328. listenerProc(request, reply);
  329. END;
  330. ELSE
  331. reply.statuscode := LONGINT( res ); (*! result type *)
  332. WebHTTP.WriteStatus(reply, out)
  333. END;
  334. out.Update; (*PH*)(* ignore out.res *)
  335. IF logEnabled THEN W3CLog(request, reply) END;
  336. IF WebHTTP.GetAdditionalFieldValue(request.additionalFields, "Connection", value) THEN
  337. closeRequested := Strings.Pos("lose", value) > 0
  338. ELSE
  339. closeRequested := FALSE
  340. END;
  341. IF (reply.statuscode >= 400) THEN
  342. INC(consecutiveErrors);
  343. IF (consecutiveErrors = MaxErrors) THEN client.Close END
  344. ELSE
  345. consecutiveErrors := 0
  346. END;
  347. END
  348. UNTIL closeRequested OR ((request.maj = 1) & (request.min = 0)) OR (client.state # TCP.Established);
  349. Terminate
  350. END HTTPAgent;
  351. ListenerProc* = PROCEDURE {DELEGATE} (request : WebHTTP.RequestHeader; response : WebHTTP.ResponseHeader);
  352. VAR
  353. http: TCPServices.Service;
  354. https: TCPServices.TLSService;
  355. hosts : Classes.List;
  356. log : AosLog.Log;
  357. hitStat : Statistics;
  358. nofRequests* : LONGINT;
  359. requestsPerMinute* : LONGINT;
  360. nofConnects* : LONGINT;
  361. defaultHost : Host;
  362. defaultPlugin: DefaultPlugin;
  363. logEnabled : BOOLEAN;
  364. logWriter : Streams.Writer;
  365. logFile : Files.File;
  366. listener* : ListenerProc;
  367. PROCEDURE GetRequests*():LONGINT;
  368. BEGIN
  369. RETURN nofRequests
  370. END GetRequests;
  371. PROCEDURE NewHTTPAgent(c: TCP.Connection; s: TCPServices.Service): TCPServices.Agent;
  372. VAR a: HTTPAgent;
  373. BEGIN
  374. NEW(a, c, s); a.secure := FALSE; RETURN a
  375. END NewHTTPAgent;
  376. PROCEDURE NewHTTPSAgent(c: TCP.Connection; s: TCPServices.Service): TCPServices.Agent;
  377. VAR a: HTTPAgent;
  378. BEGIN
  379. NEW(a, c, s); a.secure := TRUE; RETURN a
  380. END NewHTTPSAgent;
  381. PROCEDURE OpenW3CLog(CONST fn: ARRAY OF CHAR);
  382. VAR w : Files.Writer;
  383. BEGIN
  384. logFile := Files.Old(fn);
  385. IF logFile = NIL THEN
  386. logFile := Files.New(fn); Files.Register(logFile);
  387. Files.OpenWriter(w, logFile, 0);
  388. w.String("#Version: 1.0"); w.Ln;
  389. w.String("#Fields: date"); w.Char(Tab);
  390. w.String("time"); w.Char(Tab);
  391. w.String("cs-method"); w.Char(Tab);
  392. w.String("cs(host)"); w.Char(Tab);
  393. w.String("cs-uri"); w.Char(Tab);
  394. w.String("x-result"); w.Char(Tab);
  395. w.String("c-ip"); w.Char(Tab);
  396. w.String("cs(user-agent)"); w.Char(Tab);
  397. w.String("cs(referer)"); w.Ln
  398. ELSE
  399. Files.OpenWriter(w, logFile, logFile.Length())
  400. END;
  401. logWriter := w;
  402. logEnabled := TRUE
  403. END OpenW3CLog;
  404. PROCEDURE W3CLog(request : WebHTTP.RequestHeader; reply: WebHTTP.ResponseHeader);
  405. VAR time, date: LONGINT; s: ARRAY 36 OF CHAR;
  406. BEGIN {EXCLUSIVE}
  407. Clock.Get(time, date);
  408. logWriter.Date( -1, date); logWriter.Char(Tab);
  409. logWriter.Date(time, -1); logWriter.Char(Tab);
  410. WebHTTP.GetMethodName(request.method,s); logWriter.String(s);
  411. logWriter.Char(Tab);
  412. IF request.host # "" THEN logWriter.String(request.host) ELSE logWriter.String("-") END; logWriter.Char(Tab);
  413. IF request.uri # "" THEN logWriter.String(request.uri) ELSE logWriter.String("-") END; logWriter.Char(Tab);
  414. logWriter.Int(reply.statuscode, 1); logWriter.Char(Tab);
  415. IP.AdrToStr(request.fadr, s); logWriter.String(s); logWriter.Char(Tab);
  416. IF request.useragent # "" THEN logWriter.String(request.useragent) ELSE logWriter.String("-") END; logWriter.Char(Tab);
  417. IF request.referer # "" THEN logWriter.String(request.referer) ELSE logWriter.String( "-") END; logWriter.Char(Tab);
  418. logWriter.Ln
  419. END W3CLog;
  420. PROCEDURE FlushW3CLog*;
  421. BEGIN
  422. IF logEnabled THEN
  423. logWriter.Update; logFile.Update
  424. END
  425. END FlushW3CLog;
  426. PROCEDURE GetDefaultResponseHeader*(VAR r: WebHTTP.RequestHeader; VAR h: WebHTTP.ResponseHeader);
  427. BEGIN
  428. h.maj := r.maj; h.min := r.min;
  429. COPY(ServerVersion, h.server);
  430. h.statuscode := WebHTTP.OK;
  431. Strings.FormatDateTime(WebHTTP.DateTimeFormat, Dates.Now(), h.date);
  432. h.location := ""; h.contenttype := ""; h.contentlocation := ""; h.transferencoding := "";
  433. h.contentlength := -1; h.lastmodified := "";
  434. h.additionalFields := NIL
  435. END GetDefaultResponseHeader;
  436. (** Sends all availabe data from src to dst *)
  437. PROCEDURE SendData*(src: Streams.Reader; dst: Streams.Writer);
  438. VAR len: LONGINT; buf: ARRAY FileBufSize OF CHAR;
  439. BEGIN
  440. WHILE (src.res = Streams.Ok) DO
  441. src.Bytes(buf, 0, FileBufSize, len);
  442. dst.Bytes(buf, 0, len)
  443. END
  444. END SendData;
  445. (** Add a new virtual host *)
  446. PROCEDURE AddHost*(host: Host);
  447. BEGIN {EXCLUSIVE}
  448. hosts.Add(host)
  449. END AddHost;
  450. (** get a list of matching hosts (wildcards permitted, "*" returns all hosts) *)
  451. PROCEDURE FindHosts*(CONST host: ARRAY OF CHAR): HostList;
  452. VAR i: LONGINT; o: ANY; l, p, old: HostList;
  453. BEGIN {EXCLUSIVE}
  454. NEW(l);
  455. IF (host = "") THEN l.host := defaultHost;
  456. ELSE
  457. p := l; old := NIL;
  458. FOR i := 0 TO hosts.GetCount()-1 DO
  459. o := hosts.GetItem(i);
  460. IF Strings.Match(host, o(Host).name) THEN
  461. p.host := o(Host); NEW(p.next); old := p; p := p.next
  462. END
  463. END;
  464. IF (old # NIL) THEN old.next := NIL END
  465. END;
  466. IF (l.host = NIL) THEN l := NIL END;
  467. RETURN l
  468. END FindHosts;
  469. (** remove the virtual host given by name *)
  470. PROCEDURE RemoveHost*(CONST host : ARRAY OF CHAR; VAR res : WORD);
  471. VAR i : LONGINT; o, h : ANY;
  472. BEGIN {EXCLUSIVE}
  473. hosts.Lock;
  474. FOR i := 0 TO hosts.GetCount() - 1 DO
  475. o := hosts.GetItem(i); IF o(Host).name= host THEN h := o(Host) END;
  476. END;
  477. hosts.Unlock;
  478. IF (h # NIL) THEN
  479. hosts.Remove(h); res := Ok;
  480. ELSE
  481. res := Error; (* host not found *)
  482. END
  483. END RemoveHost;
  484. (** Start the basic Server functionality. *)
  485. PROCEDURE StartHTTP*(root : ARRAY OF CHAR; CONST logFile: ARRAY OF CHAR; VAR msg : ARRAY OF CHAR; VAR res : WORD);
  486. BEGIN {EXCLUSIVE}
  487. IF (http = NIL) THEN
  488. Strings.Trim(root, " "); defaultHost.SetPrefix(root);
  489. IF (logFile # "") THEN OpenW3CLog(logFile) END;
  490. NEW(http, WebHTTP.HTTPPort, NewHTTPAgent, res);
  491. IF (res = TCPServices.Ok) THEN
  492. COPY("", msg);
  493. IF Log THEN log.Enter; log.TimeStamp; log.String("Started"); log.Exit END
  494. ELSE
  495. http := NIL; COPY("TCP Error", msg);
  496. END;
  497. ELSE
  498. res := Error; COPY("HTTP server is already running", msg);
  499. END;
  500. END StartHTTP;
  501. (** Start the basic Server functionality. *)
  502. PROCEDURE StartHTTPS*(root : ARRAY OF CHAR; CONST logFile: ARRAY OF CHAR; VAR msg : ARRAY OF CHAR; VAR res : WORD);
  503. BEGIN {EXCLUSIVE}
  504. IF (https = NIL) THEN
  505. Strings.Trim(root, " "); defaultHost.SetPrefix(root);
  506. IF (logFile # "") THEN OpenW3CLog(logFile) END;
  507. NEW(https, WebHTTP.HTTPSPort, NewHTTPSAgent, res);
  508. IF (res = TCPServices.Ok) THEN
  509. COPY("", msg);
  510. IF Log THEN log.Enter; log.TimeStamp; log.String("Started"); log.Exit END
  511. ELSE
  512. https := NIL; COPY("TCP Error", msg);
  513. END;
  514. ELSE
  515. res := Error; COPY("HTTPS server is already running", msg);
  516. END
  517. END StartHTTPS;
  518. (** Stop the server *)
  519. PROCEDURE StopHTTP*(VAR msg : ARRAY OF CHAR; VAR res : WORD);
  520. BEGIN {EXCLUSIVE}
  521. IF (http # NIL) THEN
  522. res := Ok; COPY("", msg);
  523. http.Stop; http := NIL;
  524. defaultHost.SetPrefix("");
  525. IF Log THEN log.Enter; log.TimeStamp; log.String("Stopped"); log.Exit END
  526. ELSE
  527. res := Error; COPY("HTTP server is not running", msg);
  528. END;
  529. END StopHTTP;
  530. (** Stop the server *)
  531. PROCEDURE StopHTTPS*(VAR msg : ARRAY OF CHAR; VAR res : WORD);
  532. BEGIN {EXCLUSIVE}
  533. IF (https # NIL) THEN
  534. res := Ok; COPY("", msg);
  535. https.Stop; https := NIL;
  536. defaultHost.SetPrefix("");
  537. IF Log THEN log.Enter; log.TimeStamp; log.String("Stopped"); log.Exit END
  538. ELSE
  539. res := Error; COPY("HTTP server is not running", msg);
  540. END;
  541. END StopHTTPS;
  542. (** enumerate all installed hosts *)
  543. PROCEDURE ShowHosts*(out : Streams.Writer);
  544. VAR
  545. i : LONGINT; o : ANY;
  546. PROCEDURE PrintHost(h: Host);
  547. VAR p: ANY; i: LONGINT;
  548. BEGIN
  549. out.String("Host: ");
  550. IF (h.name = "") THEN out.String("default host")
  551. ELSE out.String(h.name)
  552. END;
  553. out.String("; root: '"); out.String(h.prefix); out.String("'; default: '"); out.String(h.default);
  554. out.String("'; error = '"); out.String(h.error); out.Char("'"); out.Ln;
  555. h.plugins.Lock;
  556. FOR i := 0 TO h.plugins.GetCount()-1 DO
  557. p := h.plugins.GetItem(i);
  558. out.String(" plugin: "); out.String(p(HTTPPlugin).name); out.Ln
  559. END;
  560. h.plugins.Unlock
  561. END PrintHost;
  562. BEGIN {EXCLUSIVE}
  563. ASSERT(out # NIL);
  564. hosts.Lock;
  565. PrintHost(defaultHost);
  566. FOR i := 0 TO hosts.GetCount() - 1 DO
  567. o := hosts.GetItem(i);
  568. PrintHost(o(Host))
  569. END;
  570. hosts.Unlock
  571. END ShowHosts;
  572. PROCEDURE Cleanup;
  573. VAR t: Kernel.Timer; msg : ARRAY 32 OF CHAR; ignore : WORD;
  574. BEGIN
  575. hitStat.Kill;
  576. StopHTTP(msg, ignore);
  577. StopHTTPS(msg, ignore);
  578. hosts := NIL; defaultHost := NIL;
  579. FlushW3CLog;
  580. IF Log THEN log.Close END;
  581. NEW(t); t.Sleep(100) (* avoid trap in Statistics; replace with Kernel.AwaitDeath *)
  582. END Cleanup;
  583. BEGIN
  584. IF Log THEN
  585. NEW(log, "WebHTTP Server");
  586. log.SetLogToOut(TRUE)
  587. END;
  588. listener := NIL;
  589. NEW(hosts); NEW(hitStat);
  590. NEW(defaultPlugin, "Default-Plugin");
  591. NEW(defaultHost, "");
  592. http := NIL; https := NIL;
  593. Modules.InstallTermHandler(Cleanup)
  594. END WebHTTPServer.
  595. (** INFO
  596. The HTTP server is always listening to port 80. By default all requests are handled by the default host.
  597. Content-Types are currently coded directly in HTTPPlugin.LocateResource (Types for .html .ssmp .txt .gif .jpg .pdf are known)
  598. The server can be used for multi-hosting (several different domain names resolve to the same ip number but return
  599. different pages for different domains). If a host is unknown or the request is not HTTP/1.1 compatible the default host is called.
  600. Known host-names can be dynamically added and removed. See the WebHTTPServerTools.Mod for a multi-host setup.
  601. Each host can support a number of "Plugins" that can handle special URIs like Form-Post / dynamically generated pages.
  602. See WebWormWatch.Mod for some example plugins.
  603. There is another (experimental) method for dynamically generated pages: "Server Side Modified Pages". Documents with
  604. the name extension ".ssmp" are modified by the server. The patterns "&&"<methodName>" "[<Parameters>] are replaced
  605. by the result of the respective method. See WebWormWatch.Mod WebHTTPServer.Mod WebDefaultSSMP.Mod for examples of SSMP methods.
  606. See public.info.ssmp as an example of a ".ssmp" page.
  607. (currently unavailable:)
  608. There is a helper module that allows to use url-encoded form posts. See TFHTTPServerExample.Mod for a form-post example.
  609. public.form.html contains the form.
  610. The interfaces in all these modules may change.
  611. *)
  612. COMPILE THE SERVER AND EXAMPLES
  613. PC.Compile \s TFLog.Mod WebHTTP.Mod WebHTTPServer.Mod WebSSMPPlugin.Mod WebDefaultSSMP.Mod~
  614. START THE SERVER
  615. Configuration.DoCommands
  616. WebHTTPServerTools.Start \r:../httproot \l:WebHTTP.Log ~
  617. WebHTTPServerTools.AddHost livepc.inf.ethz.ch \r:FAT:/httproot/test~
  618. WebSSMPPlugin.Install~
  619. WebDefaultSSMP.Install~
  620. WebHTTPServerTools.ListHosts~
  621. ~
  622. WebHTTPServerTools.Stop ~
  623. WebFTPServerTools.Start \r:httproot \l:httproot/FTP.Log~
  624. FREE THE SERVER
  625. System.Free WebHTTPServerTools WebDefaultSSMP WebSSMPPlugin WebHTTPServer WebHTTP~
  626. System.State WebHTTPServer~
  627. System.FreeDownTo WebHTTPServer ~
  628. FILES
  629. TFLog.Mod WebHTTP.Mod WebSSMPPlugin.Mod WebDefaultSSMP.Mod WebHTTPServer.Mod WebWormWatch.Mod public.form.html public.info.ssmp~
  630. Statistics.Log
  631. W3C Log File
  632. #Version: 1.0
  633. #Fields: date time cs-method cs(host) cs-uri c-ip cs(user-agent) cs(referer)
  634. WebHTTPServer.FlushW3CLog
  635. EditTools.OpenAscii HTTP.Log ~
  636. System.DeleteFiles HTTP.Log~