src/Pure/General/http.scala
author wenzelm
Mon, 21 Feb 2022 21:19:45 +0100
changeset 75121 2efbb4e813ad
parent 75120 488c7e8923b2
child 75145 e721880779be
permissions -rw-r--r--
clarified URL;
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
75112
899e70a9af83 tuned signature;
wenzelm
parents: 75111
diff changeset
   197
    def decode_query: Option[String] =
899e70a9af83 tuned signature;
wenzelm
parents: 75111
diff changeset
   198
      Library.try_unprefix(query, uri_name).map(Url.decode)
899e70a9af83 tuned signature;
wenzelm
parents: 75111
diff changeset
   199
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   200
    def decode_properties: Properties.T =
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   201
      space_explode('&', input.text).map(
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   202
        {
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   203
          case Properties.Eq(a, b) => Url.decode(a) -> Url.decode(b)
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   204
          case s => error("Malformed key-value pair in HTTP/POST: " + quote(s))
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   205
        })
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   206
  }
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   207
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   208
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   209
  /* response */
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   210
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   211
  object Response
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   212
  {
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   213
    def apply(
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   214
        bytes: Bytes = Bytes.empty,
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   215
        content_type: String = Content.mime_type_bytes): Response =
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   216
      new Response(bytes, content_type)
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   217
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   218
    val empty: Response = apply()
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   219
    def text(s: String): Response = apply(Bytes(s), Content.mime_type_text)
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   220
    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
   221
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   222
    def content(content: Content): Response = apply(content.bytes, content.mime_type)
75106
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   223
    def read(file: JFile): Response = content(Content.read(file))
c2570d25d512 clarified signature;
wenzelm
parents: 75105
diff changeset
   224
    def read(path: Path): Response = content(Content.read(path))
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   225
  }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   226
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   227
  class Response private[HTTP](val output: Bytes, val content_type: String)
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   228
  {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   229
    override def toString: String = output.toString
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   230
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   231
    def write(http: HttpExchange, code: Int): Unit =
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   232
    {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   233
      http.getResponseHeaders.set("Content-Type", content_type)
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   234
      http.sendResponseHeaders(code, output.length.toLong)
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   235
      using(http.getResponseBody)(output.write_stream)
65078
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   236
    }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   237
  }
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   238
2339994e8790 more operations;
wenzelm
parents: 65077
diff changeset
   239
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   240
  /* service */
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   241
75113
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   242
  abstract class Service(val service: String, method: String = "GET")
73413
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   243
  {
75113
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   244
    override def toString: String = service
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   245
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   246
    def apply(request: Request): Option[Response]
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   247
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   248
    def context(server: String): String = proper_string(url_path(server, service)).getOrElse("/")
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   249
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   250
    def handler(server: String): HttpHandler = (http: HttpExchange) => {
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   251
      val uri = http.getRequestURI
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   252
      val input = using(http.getRequestBody)(Bytes.read_stream(_))
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   253
      if (http.getRequestMethod == method) {
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   254
        val request = new Request(server, service, uri, input)
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   255
        Exn.capture(apply(request)) match {
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   256
          case Exn.Res(Some(response)) =>
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   257
            response.write(http, 200)
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   258
          case Exn.Res(None) =>
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   259
            Response.empty.write(http, 404)
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   260
          case Exn.Exn(ERROR(msg)) =>
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   261
            Response.text(Output.error_message_text(msg)).write(http, 500)
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   262
          case Exn.Exn(exn) => throw exn
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   263
        }
75113
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   264
      }
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   265
      else Response.empty.write(http, 400)
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   266
    }
73413
56c0a793cd8b clarified signature;
wenzelm
parents: 73367
diff changeset
   267
  }
66207
8d5cb4ea2b7c support for HTTP/POST method;
wenzelm
parents: 65999
diff changeset
   268
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   269
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   270
  /* server */
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   271
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   272
  class Server private[HTTP](val name: String, val http_server: HttpServer)
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   273
  {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   274
    def += (service: Service): Unit =
75108
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   275
      http_server.createContext(service.context(name), service.handler(name))
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   276
    def -= (service: Service): Unit =
75108
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   277
      http_server.removeContext(service.context(name))
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   278
73439
cb127ce2c092 tuned --- following hints by IntelliJ;
wenzelm
parents: 73438
diff changeset
   279
    def start(): Unit = http_server.start()
73367
77ef8bef0593 clarified signature --- fewer warnings;
wenzelm
parents: 73340
diff changeset
   280
    def stop(): Unit = http_server.stop(0)
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   281
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   282
    def address: InetSocketAddress = http_server.getAddress
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   283
75108
05ba781cf890 clarified signature;
wenzelm
parents: 75107
diff changeset
   284
    def url: String = "http://" + address.getHostName + ":" + address.getPort + url_path(name)
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   285
    override def toString: String = url
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   286
  }
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   287
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   288
  def server(
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   289
    name: String = UUID.random().toString,
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   290
    services: List[Service] = isabelle_services): Server =
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   291
  {
69463
6439c9024dcc clarified signature;
wenzelm
parents: 69393
diff changeset
   292
    val http_server = HttpServer.create(new InetSocketAddress(isabelle.Server.localhost, 0), 0)
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   293
    http_server.setExecutor(null)
65076
8a96ab58f016 clarified signature;
wenzelm
parents: 63823
diff changeset
   294
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   295
    val server = new Server(name, http_server)
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   296
    for (service <- services) server += service
65077
2d6e716c9d6e clarified modules;
wenzelm
parents: 65076
diff changeset
   297
    server
63823
ca8b737b08cf minimal HTTP server;
wenzelm
parents:
diff changeset
   298
  }
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   299
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   300
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   301
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   302
  /** Isabelle services **/
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   303
75113
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   304
  def isabelle_services: List[Service] =
75119
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   305
    List(new Welcome(), new Fonts(), new PDFjs(), new Docs())
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   306
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   307
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   308
  /* welcome */
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   309
75113
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   310
  class Welcome(name: String = "") extends Service(name)
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   311
  {
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   312
    def apply(request: Request): Option[Response] =
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   313
      if (request.toplevel) {
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   314
        Some(Response.text("Welcome to " + Isabelle_System.identification()))
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   315
      }
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   316
      else None
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   317
  }
65081
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   318
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   319
c20905a5bc8e handler for Isabelle version;
wenzelm
parents: 65080
diff changeset
   320
  /* fonts */
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   321
75113
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   322
  class Fonts(name: String = "fonts") extends Service(name)
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   323
  {
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   324
    private lazy val html_fonts: List[Isabelle_Fonts.Entry] =
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   325
      Isabelle_Fonts.fonts(hidden = true)
65079
8a5c2b86c5f6 handler for Isabelle fonts;
wenzelm
parents: 65078
diff changeset
   326
75113
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   327
    def apply(request: Request): Option[Response] =
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   328
      if (request.toplevel) {
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   329
        Some(Response.text(cat_lines(html_fonts.map(entry => entry.path.file_name))))
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   330
      }
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   331
      else {
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   332
        request.uri_path.flatMap(path =>
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   333
          html_fonts.collectFirst(
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   334
            { case entry if path == entry.path.base => Response(entry.bytes) }
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   335
          ))
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   336
      }
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   337
  }
75105
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   338
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   339
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   340
  /* pdfjs */
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   341
75113
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   342
  class PDFjs(name: String = "pdfjs") extends Service(name)
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   343
  {
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   344
    def apply(request: Request): Option[Response] =
75105
03115c9eea00 support for PDF.js: platform-independent PDF viewer;
wenzelm
parents: 75104
diff changeset
   345
      for {
75107
7c0217c8b8a5 clarified signature;
wenzelm
parents: 75106
diff changeset
   346
        p <- request.uri_path
75117
4b3ae1a3bbbd more robust;
wenzelm
parents: 75113
diff changeset
   347
        path = Path.explode("$ISABELLE_PDFJS_HOME") + p if path.is_file
4b3ae1a3bbbd more robust;
wenzelm
parents: 75113
diff changeset
   348
        s = p.implode if s.startsWith("build/") || s.startsWith("web/")
75113
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   349
      } yield Response.read(path)
a7a489ea4661 clarified signature;
wenzelm
parents: 75112
diff changeset
   350
  }
75119
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   351
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   352
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   353
  /* docs */
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   354
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   355
  class Docs(name: String = "docs") extends PDFjs(name)
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   356
  {
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   357
    private val doc_contents = isabelle.Doc.main_contents()
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   358
75121
2efbb4e813ad clarified URL;
wenzelm
parents: 75120
diff changeset
   359
    // example: .../docs/web/viewer.html?file=system.pdf
75119
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   360
    def doc_request(request: Request): Option[Response] =
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   361
      for {
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   362
        p <- request.uri_path if p.is_pdf
75121
2efbb4e813ad clarified URL;
wenzelm
parents: 75120
diff changeset
   363
        s = p.implode if s.startsWith("web/")
75119
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   364
        name = p.base.split_ext._1.implode
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   365
        doc <- doc_contents.docs.find(_.name == name)
75120
488c7e8923b2 clarified pdf path;
wenzelm
parents: 75119
diff changeset
   366
      } yield Response.read(doc.path)
75119
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   367
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   368
    override def apply(request: Request): Option[Response] =
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   369
      doc_request(request) orElse super.apply(request)
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   370
  }
7bf685cbc789 HTTP view of Isabelle PDF documentation;
wenzelm
parents: 75117
diff changeset
   371
}