src/Pure/General/http.scala
author wenzelm
Sun, 20 May 2018 15:05:45 +0200
changeset 68228 326f4bcc5abc
parent 67865 ab0b8e388967
child 69305 5a71b5145201
permissions -rw-r--r--
more scalable;
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 =
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    48
      using(http_exchange.getRequestBody)(Bytes.read_stream(_))
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    49
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    50
    def write_response(code: Int, response: Response)
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)
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
    54
      using(http_exchange.getResponseBody)(response.bytes.write_stream(_))
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 =
wenzelm
parents: 65079
diff changeset
    67
    new Handler(root, new HttpHandler { def handle(x: HttpExchange) { 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
  {
65080
wenzelm
parents: 65079
diff changeset
   109
    def += (handler: Handler) { http_server.createContext(handler.root, handler.handler) }
wenzelm
parents: 65079
diff changeset
   110
    def -= (handler: Handler) { 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
  {
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   122
    val localhost = InetAddress.getByName("127.0.0.1")
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   123
    val http_server = HttpServer.create(new InetSocketAddress(localhost, 0), 0)
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   124
    http_server.setExecutor(null)
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
   125
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   126
    val server = new Server(http_server)
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   127
    for (handler <- handlers) server += handler
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   128
    server
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   129
  }
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   130
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   131
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   132
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   133
  /** Isabelle resources **/
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   134
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   135
  lazy val isabelle_resources: List[Handler] =
65085
9d53b892140a clarified;
wenzelm
parents: 65084
diff changeset
   136
    List(welcome, fonts())
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   137
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   138
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   139
  /* welcome */
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   140
65085
9d53b892140a clarified;
wenzelm
parents: 65084
diff changeset
   141
  val welcome: Handler =
66479
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   142
    get("/", arg =>
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   143
      if (arg.uri.toString == "/") {
67865
ab0b8e388967 more uniform id;
wenzelm
parents: 67245
diff changeset
   144
        val id = Isabelle_System.isabelle_id()
65084
23202c455a3e tuned message;
wenzelm
parents: 65083
diff changeset
   145
        Some(Response.text("Welcome to Isabelle/" + id + ": " + Distribution.version))
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   146
      }
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   147
      else None)
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   148
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   149
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   150
  /* fonts */
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   151
65086
wenzelm
parents: 65085
diff changeset
   152
  private lazy val html_fonts: SortedMap[String, Bytes] =
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   153
    SortedMap(
65999
ee4cf96a9406 tuned signature;
wenzelm
parents: 65828
diff changeset
   154
      Isabelle_System.fonts(html = true).map(path => (path.base_name -> Bytes.read(path))): _*)
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   155
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   156
  def fonts(root: String = "/fonts"): Handler =
66479
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   157
    get(root, arg =>
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   158
      {
66479
5c0a3f63057d proper argument type (amending 8d5cb4ea2b7c);
wenzelm
parents: 66207
diff changeset
   159
        val uri_name = arg.uri.toString
65086
wenzelm
parents: 65085
diff changeset
   160
        if (uri_name == root) Some(Response.text(cat_lines(html_fonts.map(_._1))))
wenzelm
parents: 65085
diff changeset
   161
        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
   162
      })
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   163
}