src/Pure/General/http.scala
author wenzelm
Wed, 28 Nov 2018 14:51:24 +0100
changeset 69363 0675481ce575
parent 69360 dc9a39c3f75d
child 69364 91dbade9a5fa
permissions -rw-r--r--
clarified order;
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 =
69305
5a71b5145201 prefer statically-typed close operation, avoid Java 11 warning: "Illegal reflective access by scala.reflect.package$ to method sun.net.httpserver.LeftOverInputStream.close()";
wenzelm
parents: 67865
diff changeset
    48
    {
5a71b5145201 prefer statically-typed close operation, avoid Java 11 warning: "Illegal reflective access by scala.reflect.package$ to method sun.net.httpserver.LeftOverInputStream.close()";
wenzelm
parents: 67865
diff changeset
    49
      val stream = http_exchange.getRequestBody
5a71b5145201 prefer statically-typed close operation, avoid Java 11 warning: "Illegal reflective access by scala.reflect.package$ to method sun.net.httpserver.LeftOverInputStream.close()";
wenzelm
parents: 67865
diff changeset
    50
      try { Bytes.read_stream(stream) } finally { stream.close }
5a71b5145201 prefer statically-typed close operation, avoid Java 11 warning: "Illegal reflective access by scala.reflect.package$ to method sun.net.httpserver.LeftOverInputStream.close()";
wenzelm
parents: 67865
diff changeset
    51
    }
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    52
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    53
    def write_response(code: Int, response: Response)
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    54
    {
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    55
      http_exchange.getResponseHeaders().set("Content-Type", response.content_type)
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    56
      http_exchange.sendResponseHeaders(code, response.bytes.length.toLong)
69305
5a71b5145201 prefer statically-typed close operation, avoid Java 11 warning: "Illegal reflective access by scala.reflect.package$ to method sun.net.httpserver.LeftOverInputStream.close()";
wenzelm
parents: 67865
diff changeset
    57
5a71b5145201 prefer statically-typed close operation, avoid Java 11 warning: "Illegal reflective access by scala.reflect.package$ to method sun.net.httpserver.LeftOverInputStream.close()";
wenzelm
parents: 67865
diff changeset
    58
      val stream = http_exchange.getResponseBody
5a71b5145201 prefer statically-typed close operation, avoid Java 11 warning: "Illegal reflective access by scala.reflect.package$ to method sun.net.httpserver.LeftOverInputStream.close()";
wenzelm
parents: 67865
diff changeset
    59
      try { response.bytes.write_stream(stream) } finally { stream.close }
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    60
    }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    61
  }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    62
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    63
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
    64
  /* handler */
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
    65
65080
wenzelm
parents: 65079
diff changeset
    66
  class Handler private[HTTP](val root: String, val handler: HttpHandler)
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
    67
  {
65080
wenzelm
parents: 65079
diff changeset
    68
    override def toString: String = root
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
    69
  }
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
    70
65080
wenzelm
parents: 65079
diff changeset
    71
  def handler(root: String, body: Exchange => Unit): Handler =
wenzelm
parents: 65079
diff changeset
    72
    new Handler(root, new HttpHandler { def handle(x: HttpExchange) { body(new Exchange(x)) } })
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    73
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    74
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    75
  /* particular methods */
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    76
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    77
  sealed case class Arg(method: String, uri: URI, request: Bytes)
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    78
  {
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    79
    def decode_properties: Properties.T =
66923
wenzelm
parents: 66479
diff changeset
    80
      space_explode('&', request.text).map(s =>
wenzelm
parents: 66479
diff changeset
    81
        space_explode('=', s) match {
67245
caa4c9001009 tuned signature;
wenzelm
parents: 66923
diff changeset
    82
          case List(a, b) => Url.decode(a) -> Url.decode(b)
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    83
          case _ => error("Malformed key-value pair in HTTP/POST: " + quote(s))
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    84
        })
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    85
  }
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    86
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    87
  def method(name: String, root: String, body: Arg => Option[Response]): Handler =
65080
wenzelm
parents: 65079
diff changeset
    88
    handler(root, http =>
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    89
      {
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    90
        val request = http.read_request()
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    91
        if (http.request_method == name) {
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    92
          val arg = Arg(name, http.request_uri, request)
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
    93
          Exn.capture(body(arg)) match {
65087
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
    94
            case Exn.Res(Some(response)) =>
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
    95
              http.write_response(200, response)
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
    96
            case Exn.Res(None) =>
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
    97
              http.write_response(404, Response.empty)
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
    98
            case Exn.Exn(ERROR(msg)) =>
65828
02dd430d80c5 tuned signature;
wenzelm
parents: 65088
diff changeset
    99
              http.write_response(500, Response.text(Output.error_message_text(msg)))
65087
11e332c4e39f clarified errors;
wenzelm
parents: 65086
diff changeset
   100
            case Exn.Exn(exn) => throw exn
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   101
          }
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   102
        }
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   103
        else http.write_response(400, Response.empty)
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   104
      })
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
   105
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   106
  def get(root: String, body: Arg => Option[Response]): Handler = method("GET", root, body)
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   107
  def post(root: String, body: Arg => Option[Response]): Handler = method("POST", root, body)
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   108
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   109
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   110
  /* server */
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   111
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   112
  class Server private[HTTP](val http_server: HttpServer)
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   113
  {
65080
wenzelm
parents: 65079
diff changeset
   114
    def += (handler: Handler) { http_server.createContext(handler.root, handler.handler) }
wenzelm
parents: 65079
diff changeset
   115
    def -= (handler: Handler) { http_server.removeContext(handler.root) }
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   116
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   117
    def start: Unit = http_server.start
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   118
    def stop: Unit = http_server.stop(0)
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   119
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   120
    def address: InetSocketAddress = http_server.getAddress
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   121
    def url: String = "http://" + address.getHostName + ":" + address.getPort
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   122
    override def toString: String = url
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   123
  }
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   124
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   125
  def server(handlers: List[Handler] = isabelle_resources): Server =
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   126
  {
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   127
    val localhost = InetAddress.getByName("127.0.0.1")
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   128
    val http_server = HttpServer.create(new InetSocketAddress(localhost, 0), 0)
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   129
    http_server.setExecutor(null)
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
   130
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   131
    val server = new Server(http_server)
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   132
    for (handler <- handlers) server += handler
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   133
    server
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   134
  }
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   135
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   136
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   137
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   138
  /** Isabelle resources **/
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   139
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   140
  lazy val isabelle_resources: List[Handler] =
65085
9d53b892140a clarified;
wenzelm
parents: 65084
diff changeset
   141
    List(welcome, fonts())
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   142
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   143
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   144
  /* welcome */
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   145
65085
9d53b892140a clarified;
wenzelm
parents: 65084
diff changeset
   146
  val welcome: Handler =
66479
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   147
    get("/", arg =>
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   148
      if (arg.uri.toString == "/") {
67865
ab0b8e388967 more uniform id;
wenzelm
parents: 67245
diff changeset
   149
        val id = Isabelle_System.isabelle_id()
65084
23202c455a3e tuned message;
wenzelm
parents: 65083
diff changeset
   150
        Some(Response.text("Welcome to Isabelle/" + id + ": " + Distribution.version))
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   151
      }
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   152
      else None)
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   153
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   154
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   155
  /* fonts */
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   156
69363
0675481ce575 clarified order;
wenzelm
parents: 69360
diff changeset
   157
  private lazy val html_fonts: List[(String, Bytes)] =
0675481ce575 clarified order;
wenzelm
parents: 69360
diff changeset
   158
    Isabelle_Fonts.fonts(html = true).map(entry => (entry.name -> entry.bytes))
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   159
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   160
  def fonts(root: String = "/fonts"): Handler =
66479
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   161
    get(root, arg =>
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   162
      {
66479
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   163
        val uri_name = arg.uri.toString
65086
wenzelm
parents: 65085
diff changeset
   164
        if (uri_name == root) Some(Response.text(cat_lines(html_fonts.map(_._1))))
wenzelm
parents: 65085
diff changeset
   165
        else html_fonts.collectFirst({ case (a, b) if uri_name == root + "/" + a => Response(b) })
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   166
      })
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   167
}