src/Pure/General/http.scala
author wenzelm
Mon, 01 Mar 2021 22:22:12 +0100
changeset 73340 0ffcad1f6130
parent 69463 6439c9024dcc
child 73367 77ef8bef0593
permissions -rw-r--r--
tuned --- fewer warnings;
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
     1
/*  Title:      Pure/General/http.scala
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
     2
    Author:     Makarius
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
     3
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
     4
HTTP server support.
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
     5
*/
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
     6
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
     7
package isabelle
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
     8
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
     9
67245
caa4c9001009 tuned signature;
wenzelm
parents: 66923
diff changeset
    10
import java.net.{InetAddress, InetSocketAddress, URI}
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
    11
import com.sun.net.httpserver.{HttpExchange, HttpHandler, HttpServer}
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
    12
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
    13
import scala.collection.immutable.SortedMap
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
    14
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
    15
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
    16
object HTTP
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
    17
{
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
    18
  /** server **/
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
    19
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    20
  /* response */
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    21
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    22
  object Response
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    23
  {
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    24
    def apply(
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    25
        bytes: Bytes = Bytes.empty,
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    26
        content_type: String = "application/octet-stream"): Response =
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    27
      new Response(bytes, content_type)
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    28
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    29
    val empty: Response = apply()
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    30
    def text(s: String): Response = apply(Bytes(s), "text/plain; charset=utf-8")
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    31
    def html(s: String): Response = apply(Bytes(s), "text/html; charset=utf-8")
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    32
  }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    33
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    34
  class Response private[HTTP](val bytes: Bytes, val content_type: String)
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    35
  {
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    36
    override def toString: String = bytes.toString
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    37
  }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    38
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    39
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    40
  /* exchange */
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    41
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    42
  class Exchange private[HTTP](val http_exchange: HttpExchange)
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    43
  {
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    44
    def request_method: String = http_exchange.getRequestMethod
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    45
    def request_uri: URI = http_exchange.getRequestURI
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    46
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    47
    def read_request(): Bytes =
69393
ed0824ef337e static type for Library.using: avoid Java 11 warnings on "Illegal reflective access";
wenzelm
parents: 69374
diff changeset
    48
      using(http_exchange.getRequestBody)(Bytes.read_stream(_))
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    49
73340
0ffcad1f6130 tuned --- fewer warnings;
wenzelm
parents: 69463
diff changeset
    50
    def write_response(code: Int, response: Response): Unit =
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    51
    {
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    52
      http_exchange.getResponseHeaders().set("Content-Type", response.content_type)
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    53
      http_exchange.sendResponseHeaders(code, response.bytes.length.toLong)
69393
ed0824ef337e static type for Library.using: avoid Java 11 warnings on "Illegal reflective access";
wenzelm
parents: 69374
diff changeset
    54
      using(http_exchange.getResponseBody)(response.bytes.write_stream(_))
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    55
    }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    56
  }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    57
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    58
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
    59
  /* handler */
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
    60
65080
wenzelm
parents: 65079
diff changeset
    61
  class Handler private[HTTP](val root: String, val handler: HttpHandler)
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
    62
  {
65080
wenzelm
parents: 65079
diff changeset
    63
    override def toString: String = root
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
    64
  }
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
    65
65080
wenzelm
parents: 65079
diff changeset
    66
  def handler(root: String, body: Exchange => Unit): Handler =
73340
0ffcad1f6130 tuned --- fewer warnings;
wenzelm
parents: 69463
diff changeset
    67
    new Handler(root, new HttpHandler { def handle(x: HttpExchange): Unit = body(new Exchange(x)) })
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    68
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    69
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    70
  /* particular methods */
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    71
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    72
  sealed case class Arg(method: String, uri: URI, request: Bytes)
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    73
  {
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    74
    def decode_properties: Properties.T =
66923
wenzelm
parents: 66479
diff changeset
    75
      space_explode('&', request.text).map(s =>
wenzelm
parents: 66479
diff changeset
    76
        space_explode('=', s) match {
67245
caa4c9001009 tuned signature;
wenzelm
parents: 66923
diff changeset
    77
          case List(a, b) => Url.decode(a) -> Url.decode(b)
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    78
          case _ => error("Malformed key-value pair in HTTP/POST: " + quote(s))
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    79
        })
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    80
  }
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    81
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    82
  def method(name: String, root: String, body: Arg => Option[Response]): Handler =
65080
wenzelm
parents: 65079
diff changeset
    83
    handler(root, http =>
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    84
      {
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    85
        val request = http.read_request()
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    86
        if (http.request_method == name) {
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    87
          val arg = Arg(name, http.request_uri, request)
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    88
          Exn.capture(body(arg)) match {
65087
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
    89
            case Exn.Res(Some(response)) =>
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
    90
              http.write_response(200, response)
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
    91
            case Exn.Res(None) =>
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
    92
              http.write_response(404, Response.empty)
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
    93
            case Exn.Exn(ERROR(msg)) =>
65828
02dd430d80c5 tuned signature;
wenzelm
parents: 65088
diff changeset
    94
              http.write_response(500, Response.text(Output.error_message_text(msg)))
65087
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
    95
            case Exn.Exn(exn) => throw exn
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    96
          }
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    97
        }
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    98
        else http.write_response(400, Response.empty)
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    99
      })
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
   100
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   101
  def get(root: String, body: Arg => Option[Response]): Handler = method("GET", root, body)
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   102
  def post(root: String, body: Arg => Option[Response]): Handler = method("POST", root, body)
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   103
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   104
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   105
  /* server */
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   106
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   107
  class Server private[HTTP](val http_server: HttpServer)
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   108
  {
73340
0ffcad1f6130 tuned --- fewer warnings;
wenzelm
parents: 69463
diff changeset
   109
    def += (handler: Handler): Unit = http_server.createContext(handler.root, handler.handler)
0ffcad1f6130 tuned --- fewer warnings;
wenzelm
parents: 69463
diff changeset
   110
    def -= (handler: Handler): Unit = http_server.removeContext(handler.root)
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   111
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   112
    def start: Unit = http_server.start
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   113
    def stop: Unit = http_server.stop(0)
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   114
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   115
    def address: InetSocketAddress = http_server.getAddress
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   116
    def url: String = "http://" + address.getHostName + ":" + address.getPort
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   117
    override def toString: String = url
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   118
  }
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   119
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   120
  def server(handlers: List[Handler] = isabelle_resources): Server =
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   121
  {
69463
6439c9024dcc clarified signature;
wenzelm
parents: 69393
diff changeset
   122
    val http_server = HttpServer.create(new InetSocketAddress(isabelle.Server.localhost, 0), 0)
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   123
    http_server.setExecutor(null)
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
   124
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   125
    val server = new Server(http_server)
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   126
    for (handler <- handlers) server += handler
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   127
    server
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   128
  }
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   129
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   130
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   131
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   132
  /** Isabelle resources **/
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   133
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   134
  lazy val isabelle_resources: List[Handler] =
65085
9d53b892140a clarified;
wenzelm
parents: 65084
diff changeset
   135
    List(welcome, fonts())
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   136
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   137
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   138
  /* welcome */
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   139
65085
9d53b892140a clarified;
wenzelm
parents: 65084
diff changeset
   140
  val welcome: Handler =
66479
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   141
    get("/", arg =>
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   142
      if (arg.uri.toString == "/") {
67865
ab0b8e388967 more uniform id;
wenzelm
parents: 67245
diff changeset
   143
        val id = Isabelle_System.isabelle_id()
65084
23202c455a3e tuned message;
wenzelm
parents: 65083
diff changeset
   144
        Some(Response.text("Welcome to Isabelle/" + id + ": " + Distribution.version))
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   145
      }
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   146
      else None)
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   147
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   148
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   149
  /* fonts */
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   150
69374
ab66951166f3 clarified "hidden" terminology;
wenzelm
parents: 69366
diff changeset
   151
  private lazy val html_fonts: List[Isabelle_Fonts.Entry] =
ab66951166f3 clarified "hidden" terminology;
wenzelm
parents: 69366
diff changeset
   152
    Isabelle_Fonts.fonts(hidden = true)
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   153
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   154
  def fonts(root: String = "/fonts"): Handler =
66479
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   155
    get(root, arg =>
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   156
      {
66479
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   157
        val uri_name = arg.uri.toString
69364
91dbade9a5fa proper font file name for HTTP (amending dc9a39c3f75d);
wenzelm
parents: 69363
diff changeset
   158
        if (uri_name == root) {
69366
b6dacf6eabe3 clarified signature;
wenzelm
parents: 69364
diff changeset
   159
          Some(Response.text(cat_lines(html_fonts.map(entry => entry.path.file_name))))
69364
91dbade9a5fa proper font file name for HTTP (amending dc9a39c3f75d);
wenzelm
parents: 69363
diff changeset
   160
        }
91dbade9a5fa proper font file name for HTTP (amending dc9a39c3f75d);
wenzelm
parents: 69363
diff changeset
   161
        else {
91dbade9a5fa proper font file name for HTTP (amending dc9a39c3f75d);
wenzelm
parents: 69363
diff changeset
   162
          html_fonts.collectFirst(
69366
b6dacf6eabe3 clarified signature;
wenzelm
parents: 69364
diff changeset
   163
            { case entry if uri_name == root + "/" + entry.path.file_name => Response(entry.bytes) })
69364
91dbade9a5fa proper font file name for HTTP (amending dc9a39c3f75d);
wenzelm
parents: 69363
diff changeset
   164
        }
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   165
      })
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   166
}