src/Pure/General/http.scala
author wenzelm
Mon, 21 Feb 2022 13:19:30 +0100
changeset 75111 ecaac5050ec2
parent 75110 3c8f24e9eff1
child 75112 899e70a9af83
permissions -rw-r--r--
tuned;
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
73438
69d449f0ca04 tuned comments;
wenzelm
parents: 73430
diff changeset
     4
HTTP client and 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
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    10
import java.io.{File => JFile}
75104
08bb0d32b2e3 more robust mime_type;
wenzelm
parents: 74945
diff changeset
    11
import java.nio.file.Files
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
    12
import java.net.{InetSocketAddress, URI, URL, HttpURLConnection}
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
    13
import com.sun.net.httpserver.{HttpExchange, HttpHandler, HttpServer}
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
    14
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
{
73416
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    18
  /** content **/
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    19
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    20
  object Content
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    21
  {
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    22
    val mime_type_bytes: String = "application/octet-stream"
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    23
    val mime_type_text: String = "text/plain; charset=utf-8"
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    24
    val mime_type_html: String = "text/html; charset=utf-8"
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    25
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    26
    val default_mime_type: String = mime_type_bytes
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    27
    val default_encoding: String = UTF8.charset_name
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    28
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    29
    def apply(
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    30
        bytes: Bytes,
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    31
        file_name: String = "",
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    32
        mime_type: String = default_mime_type,
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    33
        encoding: String = default_encoding,
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    34
        elapsed_time: Time = Time.zero): Content =
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    35
      new Content(bytes, file_name, mime_type, encoding, elapsed_time)
73416
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    36
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    37
    def read(file: JFile): Content =
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    38
    {
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    39
      val bytes = Bytes.read(file)
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    40
      val file_name = file.getName
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    41
      val mime_type = Option(Files.probeContentType(file.toPath)).getOrElse(default_mime_type)
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    42
      apply(bytes, file_name = file_name, mime_type = mime_type)
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    43
    }
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    44
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    45
    def read(path: Path): Content = read(path.file)
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    46
  }
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    47
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    48
  class Content private(
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    49
    val bytes: Bytes,
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    50
    val file_name: String,
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    51
    val mime_type: String,
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    52
    val encoding: String,
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    53
    val elapsed_time: Time)
73416
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    54
  {
73440
3696bb4085ed tuned signature (again);
wenzelm
parents: 73439
diff changeset
    55
    def text: String = new String(bytes.array, encoding)
74945
4dc90b43ba94 support for Flarum server;
wenzelm
parents: 74094
diff changeset
    56
    def json: JSON.T = JSON.parse(text)
73416
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    57
  }
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    58
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    59
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    60
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    61
  /** client **/
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    62
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    63
  val NEWLINE: String = "\r\n"
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    64
73416
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    65
  object Client
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
    66
  {
73439
cb127ce2c092 tuned --- following hints by IntelliJ;
wenzelm
parents: 73438
diff changeset
    67
    val default_timeout: Time = Time.seconds(180)
73422
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
    68
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
    69
    def open_connection(url: URL,
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
    70
      timeout: Time = default_timeout,
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
    71
      user_agent: String = ""): HttpURLConnection =
73421
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
    72
    {
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
    73
      url.openConnection match {
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
    74
        case connection: HttpURLConnection =>
73422
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
    75
          if (0 < timeout.ms && timeout.ms <= Integer.MAX_VALUE) {
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
    76
            val ms = timeout.ms.toInt
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
    77
            connection.setConnectTimeout(ms)
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
    78
            connection.setReadTimeout(ms)
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
    79
          }
73421
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
    80
          proper_string(user_agent).foreach(s => connection.setRequestProperty("User-Agent", s))
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
    81
          connection
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
    82
        case _ => error("Bad URL (not HTTP): " + quote(url.toString))
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
    83
      }
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
    84
    }
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    85
73421
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
    86
    def get_content(connection: HttpURLConnection): Content =
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    87
    {
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    88
      val Charset = """.*\bcharset="?([\S^"]+)"?.*""".r
73429
8970081c6500 elapsed time to download content (and for the server to provide content);
wenzelm
parents: 73425
diff changeset
    89
8970081c6500 elapsed time to download content (and for the server to provide content);
wenzelm
parents: 73425
diff changeset
    90
      val start = Time.now()
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    91
      using(connection.getInputStream)(stream =>
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    92
      {
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    93
        val bytes = Bytes.read_stream(stream, hint = connection.getContentLength)
73429
8970081c6500 elapsed time to download content (and for the server to provide content);
wenzelm
parents: 73425
diff changeset
    94
        val stop = Time.now()
8970081c6500 elapsed time to download content (and for the server to provide content);
wenzelm
parents: 73425
diff changeset
    95
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    96
        val file_name = Url.file_name(connection.getURL)
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
    97
        val mime_type = Option(connection.getContentType).getOrElse(Content.default_mime_type)
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    98
        val encoding =
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
    99
          (connection.getContentEncoding, mime_type) match {
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   100
            case (enc, _) if enc != null => enc
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   101
            case (_, Charset(enc)) => enc
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   102
            case _ => Content.default_encoding
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   103
          }
73429
8970081c6500 elapsed time to download content (and for the server to provide content);
wenzelm
parents: 73425
diff changeset
   104
        Content(bytes, file_name = file_name, mime_type = mime_type,
8970081c6500 elapsed time to download content (and for the server to provide content);
wenzelm
parents: 73425
diff changeset
   105
          encoding = encoding, elapsed_time = stop - start)
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   106
      })
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   107
    }
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   108
73422
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
   109
    def get(url: URL, timeout: Time = default_timeout, user_agent: String = ""): Content =
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
   110
      get_content(open_connection(url, timeout = timeout, user_agent = user_agent))
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   111
73422
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
   112
    def post(url: URL, parameters: List[(String, Any)],
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
   113
      timeout: Time = default_timeout,
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
   114
      user_agent: String = ""): Content =
73416
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
   115
    {
73422
fc7a0ea94c43 support timeout, similar to perl LWP::UserAgent;
wenzelm
parents: 73421
diff changeset
   116
      val connection = open_connection(url, timeout = timeout, user_agent = user_agent)
73421
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   117
      connection.setRequestMethod("POST")
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   118
      connection.setDoOutput(true)
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   119
74094
6113f1db4342 clarified signature;
wenzelm
parents: 73716
diff changeset
   120
      val boundary = UUID.random().toString
73421
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   121
      connection.setRequestProperty(
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   122
        "Content-Type", "multipart/form-data; boundary=" + quote(boundary))
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   123
73421
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   124
      using(connection.getOutputStream)(out =>
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   125
      {
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   126
        def output(s: String): Unit = out.write(UTF8.bytes(s))
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   127
        def output_newline(n: Int = 1): Unit = (1 to n).foreach(_ => output(NEWLINE))
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   128
        def output_boundary(end: Boolean = false): Unit =
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   129
          output("--" + boundary + (if (end) "--" else "") + NEWLINE)
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   130
        def output_name(name: String): Unit =
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   131
          output("Content-Disposition: form-data; name=" + quote(name))
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   132
        def output_value(value: Any): Unit =
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   133
        {
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   134
          output_newline(2)
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   135
          output(value.toString)
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   136
        }
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   137
        def output_content(content: Content): Unit =
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   138
        {
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   139
          proper_string(content.file_name).foreach(s => output("; filename=" + quote(s)))
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   140
          output_newline()
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   141
          proper_string(content.mime_type).foreach(s => output("Content-Type: " + s))
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   142
          output_newline(2)
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   143
          content.bytes.write_stream(out)
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   144
        }
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   145
73421
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   146
        output_newline(2)
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   147
73421
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   148
        for { (name, value) <- parameters } {
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   149
          output_boundary()
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   150
          output_name(name)
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   151
          value match {
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   152
            case content: Content => output_content(content)
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   153
            case file: JFile => output_content(Content.read(file))
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   154
            case path: Path => output_content(Content.read(path))
73421
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   155
            case _ => output_value(value)
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   156
          }
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   157
          output_newline()
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   158
        }
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   159
        output_boundary(end = true)
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   160
        out.flush()
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   161
      })
73417
1dcc2b228b8b clarified HTTP.Content: support encoding;
wenzelm
parents: 73416
diff changeset
   162
73421
a114ecd280ca clarified signature;
wenzelm
parents: 73417
diff changeset
   163
      get_content(connection)
73416
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
   164
    }
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
   165
  }
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
   166
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
   167
08aa4c1ed579 clarified signature: more explicit HTTP operations;
wenzelm
parents: 73413
diff changeset
   168
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   169
  /** server **/
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   170
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   171
  /* request */
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   172
75108
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   173
  def url_path(names: String*): String =
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   174
    names.iterator.flatMap(a => if (a.isEmpty) None else Some("/" + a)).mkString
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   175
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   176
  class Request private[HTTP](
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   177
    val server: String,
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   178
    val service: String,
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   179
    val uri: URI,
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   180
    val input: Bytes)
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   181
  {
75108
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   182
    def home: String = url_path(server, service)
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   183
    def root: String = home + "/"
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   184
    def query: String = home + "?"
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   185
75109
e6162afc5460 more robust toplevel url: allow extra "/";
wenzelm
parents: 75108
diff changeset
   186
    def toplevel: Boolean = uri_name == home || uri_name == root
e6162afc5460 more robust toplevel url: allow extra "/";
wenzelm
parents: 75108
diff changeset
   187
e6162afc5460 more robust toplevel url: allow extra "/";
wenzelm
parents: 75108
diff changeset
   188
    val uri_name: String = uri.toString
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   189
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   190
    def uri_path: Option[Path] =
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   191
      for {
75111
wenzelm
parents: 75110
diff changeset
   192
        s <- Option(uri.getPath).flatMap(Library.try_unprefix(root, _))
wenzelm
parents: 75110
diff changeset
   193
        if Path.is_wellformed(s)
wenzelm
parents: 75110
diff changeset
   194
        p = Path.explode(s) if p.all_basic
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   195
      } yield p
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   196
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   197
    def decode_properties: Properties.T =
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   198
      space_explode('&', input.text).map(
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   199
        {
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   200
          case Properties.Eq(a, b) => Url.decode(a) -> Url.decode(b)
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   201
          case s => error("Malformed key-value pair in HTTP/POST: " + quote(s))
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   202
        })
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   203
  }
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   204
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   205
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   206
  /* response */
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   207
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   208
  object Response
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   209
  {
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   210
    def apply(
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   211
        bytes: Bytes = Bytes.empty,
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   212
        content_type: String = Content.mime_type_bytes): Response =
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   213
      new Response(bytes, content_type)
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   214
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   215
    val empty: Response = apply()
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   216
    def text(s: String): Response = apply(Bytes(s), Content.mime_type_text)
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   217
    def html(s: String): Response = apply(Bytes(s), Content.mime_type_html)
75105
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   218
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   219
    def content(content: Content): Response = apply(content.bytes, content.mime_type)
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   220
    def read(file: JFile): Response = content(Content.read(file))
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   221
    def read(path: Path): Response = content(Content.read(path))
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   222
  }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   223
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   224
  class Response private[HTTP](val output: Bytes, val content_type: String)
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   225
  {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   226
    override def toString: String = output.toString
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   227
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   228
    def write(http: HttpExchange, code: Int): Unit =
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   229
    {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   230
      http.getResponseHeaders.set("Content-Type", content_type)
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   231
      http.sendResponseHeaders(code, output.length.toLong)
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   232
      using(http.getResponseBody)(output.write_stream)
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   233
    }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   234
  }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   235
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   236
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   237
  /* service */
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   238
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   239
  type Exchange = Request => Option[Response]
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   240
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   241
  object Service
73413
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   242
  {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   243
    def apply(method: String, name: String, body: Exchange): Service =
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   244
      new Service(name: String) {
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   245
        def handler(server: String): HttpHandler = (http: HttpExchange) =>
73413
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   246
        {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   247
          val uri = http.getRequestURI
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   248
          val input = using(http.getRequestBody)(Bytes.read_stream(_))
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   249
          if (http.getRequestMethod == method) {
75108
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   250
            val request = new Request(server, name, uri, input)
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   251
            Exn.capture(body(request)) match {
73413
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   252
              case Exn.Res(Some(response)) =>
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   253
                response.write(http, 200)
73413
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   254
              case Exn.Res(None) =>
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   255
                Response.empty.write(http, 404)
73413
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   256
              case Exn.Exn(ERROR(msg)) =>
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   257
                Response.text(Output.error_message_text(msg)).write(http, 500)
73413
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   258
              case Exn.Exn(exn) => throw exn
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   259
            }
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   260
          }
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   261
          else Response.empty.write(http, 400)
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   262
        }
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   263
    }
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
   264
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   265
    def get(name: String, body: Exchange): Service = apply("GET", name, body)
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   266
    def post(name: String, body: Exchange): Service = apply("POST", name, body)
73413
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   267
  }
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   268
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   269
  abstract class Service private[HTTP](val name: String)
73413
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   270
  {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   271
    override def toString: String = name
75108
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   272
    def context(server: String): String = proper_string(url_path(server, name)).getOrElse("/")
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   273
    def handler(server: String): HttpHandler
73413
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   274
  }
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   275
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   276
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   277
  /* server */
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   278
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   279
  class Server private[HTTP](val name: String, val http_server: HttpServer)
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   280
  {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   281
    def += (service: Service): Unit =
75108
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   282
      http_server.createContext(service.context(name), service.handler(name))
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   283
    def -= (service: Service): Unit =
75108
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   284
      http_server.removeContext(service.context(name))
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   285
73439
cb127ce2c092 tuned --- following hints by IntelliJ;
wenzelm
parents: 73438
diff changeset
   286
    def start(): Unit = http_server.start()
73367
77ef8bef0593 clarified signature --- fewer warnings;
wenzelm
parents: 73340
diff changeset
   287
    def stop(): Unit = http_server.stop(0)
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   288
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   289
    def address: InetSocketAddress = http_server.getAddress
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   290
75108
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   291
    def url: String = "http://" + address.getHostName + ":" + address.getPort + url_path(name)
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   292
    override def toString: String = url
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   293
  }
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   294
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   295
  def server(
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   296
    name: String = UUID.random().toString,
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   297
    services: List[Service] = isabelle_services): Server =
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   298
  {
69463
6439c9024dcc clarified signature;
wenzelm
parents: 69393
diff changeset
   299
    val http_server = HttpServer.create(new InetSocketAddress(isabelle.Server.localhost, 0), 0)
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   300
    http_server.setExecutor(null)
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
   301
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   302
    val server = new Server(name, http_server)
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   303
    for (service <- services) server += service
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   304
    server
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   305
  }
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   306
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   307
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   308
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   309
  /** Isabelle services **/
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   310
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   311
  def isabelle_services: List[Service] = List(welcome(), fonts(), pdfjs())
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   312
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   313
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   314
  /* welcome */
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   315
75110
3c8f24e9eff1 clarified URL (again);
wenzelm
parents: 75109
diff changeset
   316
  def welcome(name: String = ""): Service =
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   317
    Service.get(name, request =>
75109
e6162afc5460 more robust toplevel url: allow extra "/";
wenzelm
parents: 75108
diff changeset
   318
      {
e6162afc5460 more robust toplevel url: allow extra "/";
wenzelm
parents: 75108
diff changeset
   319
        if (request.toplevel) {
e6162afc5460 more robust toplevel url: allow extra "/";
wenzelm
parents: 75108
diff changeset
   320
          Some(Response.text("Welcome to " + Isabelle_System.identification()))
e6162afc5460 more robust toplevel url: allow extra "/";
wenzelm
parents: 75108
diff changeset
   321
        }
e6162afc5460 more robust toplevel url: allow extra "/";
wenzelm
parents: 75108
diff changeset
   322
        else None
e6162afc5460 more robust toplevel url: allow extra "/";
wenzelm
parents: 75108
diff changeset
   323
      })
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   324
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   325
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   326
  /* fonts */
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   327
69374
ab66951166f3 clarified "hidden" terminology;
wenzelm
parents: 69366
diff changeset
   328
  private lazy val html_fonts: List[Isabelle_Fonts.Entry] =
ab66951166f3 clarified "hidden" terminology;
wenzelm
parents: 69366
diff changeset
   329
    Isabelle_Fonts.fonts(hidden = true)
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   330
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   331
  def fonts(name: String = "fonts"): Service =
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   332
    Service.get(name, request =>
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   333
      {
75109
e6162afc5460 more robust toplevel url: allow extra "/";
wenzelm
parents: 75108
diff changeset
   334
        if (request.toplevel) {
69366
b6dacf6eabe3 clarified signature;
wenzelm
parents: 69364
diff changeset
   335
          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
   336
        }
91dbade9a5fa proper font file name for HTTP (amending dc9a39c3f75d);
wenzelm
parents: 69363
diff changeset
   337
        else {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   338
          request.uri_path.flatMap(path =>
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   339
            html_fonts.collectFirst(
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   340
              { case entry if path == entry.path.base => Response(entry.bytes) }
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   341
            ))
69364
91dbade9a5fa proper font file name for HTTP (amending dc9a39c3f75d);
wenzelm
parents: 69363
diff changeset
   342
        }
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   343
      })
75105
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   344
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   345
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   346
  /* pdfjs */
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   347
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   348
  def pdfjs(name: String = "pdfjs"): Service =
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   349
    Service.get(name, request =>
75105
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   350
      for {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   351
        p <- request.uri_path
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   352
        path = Path.explode("$ISABELLE_PDFJS_HOME") + p
75105
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   353
        if path.is_file
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   354
      } yield Response.read(path))
75105
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   355
}