NaviServer 4.99.16


New Features:

  - ns_cache improvements:

    * Allow runtime reaction and re-configuration of caches
      (e.g. grow/shrink a cache via new cmd ns_cache_configure)

    * Added cache transaction semantics

      Background: When ns_cache_* commands are used within a database
      transaction (e.g. in OpenACS), it might occur, that partial
      results of the transaction are cached before the transaction is
      committed.  When the transaction is rolled back, invalid values
      might be kept in the stack leading to erroneous and hard to
      debug behavior.  Furthermore, information about changes might
      leak into other concurrent threads via the cache, even before
      the transaction is committed.

      The new cache transaction semantics is implemented via the three

     - ns_cache_transaction_begin
     - ns_cache_transaction_commit
     - ns_cache_transaction_rollback

    When no ns_cache_transaction* commands are used, the behavior is
    exactly as before.

    When ns_cache_transaction* commands are in use, the following
    functionalities become available

     - The ability to rollback of the values since the matching

     - Isolation of behavior: cached values from incomplete cache
       transactions are just visible from the current thread, but
       not from other threads.

     - Nesting: transactions can be nested
       (up to a compile time constant, per default: 16)

     - Introspection: the statistics about cache commits and
       rollbacks are included in the cache statistics.

    * Support caches sizes larger than 2GB

    * Summary of new ns_cache* commands
      . ns_cache_configure to change cache parameters at runtime
      .    ns_cache_exists: This command is is more than an order of
    magnitude faster than [expr {"foo" in [ns_cache_names]]
      . ns_cache_transaction_begin
      . ns_cache_transaction_commit
      . ns_cache_transaction_rollback

  - Virtual server improvements

    Make mapping of host entries in the virtual server definition more
    intelligent to avoid newbie gotchas and hard to find
    mis-configurations (entries in e.g. nssock/servers)
     a) add automatically an entry with the port, when non is given
     b) complain, when driver is not listening on the specified port
     c) add automatically an entry without the port, when the driver
       listening on the default port.
    Old configurations (doing a-z manually) should continue to work

  - Improved integration of NaviServer with e.g service files (systemd):
    When the server is starting in a forking mode, return a non-zero
    return code, when initialization of the server went wrong (e.g.
    error in the config file)

  - nsdb:
    provide interface for obtaining session_ids. When implementing
    e.g. prepared statements over the nsdbpg driver, the prepared
    statement is just valid for one session. by providing the
    session_id on the Tcl layer, prepared statements can be provided
    selectively on the Tcl level.

  - https:
    * Support OpenSSL 1.1.0 (configuration and runtime)
    * Support LibreSSL 2.6.3 and 2.6.4
    * Improved support for ancient versions of OpenSSL (e.g. in CentOS 5.0)
    * Added support for for client side Server Name Indication (SNI)
    * Report attempted TLS handshakes on plain connections to error.log

  - nscgi: new config parameter "allowstaticresources"

    New parameter "allowstaticresources": provide control on whether
    static resources can be delivered from a cgi-bin directory.

    Background: When CGI scripts are called from a cgi-bin directory
    without an explicit extension mapping to a script interpreter
    (e.g.  Perl, Bash, ...) the script has to be set executable. If
    this is not the case, the CGI script is delivered in source code,
    which might reveal unwanted information. The reason for this
    behavior was, that one can so deliver also static resources
    (e.g. images) from a cgi-bin directory. Now this behavior con be
    controlled with the configure parameter "allowstaticresources",
    which is by default off.  If an application depends on this
    behavior, please turn it on in the config file (in the nscgi

  - nsproxy:
    New subcommand "ns_proxy stats" to provide usage and runtime
    statistics per nsproxy pool.

  - logging:

    * Better error.log entries: Include the connection id in the
      thread name to ease debugging, when multiple requests of the
      same connection thread write to the error.log and it is not
      clear, where the first ends and the next starts.

    * Write notice message to error.log when entities are too large
      to provide information about possible misconfigurations or
      attacks in the error log

    * Added new config parameter for nslog: "logthreadname"

      When the parameter is specified, the thread name is placed as
      second field in the access.log. This eases debugging, since one
      can now link the lines in error.log caused by an request with
      the line in the access.log without the need of guessing via
      time-stamps. The thread name contains the the name of the
      connection pool and a connection id.

   *  When "mutexlocktrace" is activated, use thread name instead
      of id to ease debugging

  - Added parameter "defaultextension" for adp section in config file.
    Can be set e.g. to ".adp" to test for FILENAME.adp, when adp is
    mapped to FILENAME

Performance Improvements:

  * Improved performance of ns_urlencode/ns_urldecode in common
    cases by about a factor of 2

  * Improved performance by replacing the slow "snprintf(... %d ...)"
    function by a the new functions ns_uint32toa() and ns_uint64toa().
    This change improved the performance of EnterSet() by nearly a
    factor of 2.5. Before, snprintf() took in this function 3x (!) as
    long as Tcl_CreateHashEntry(); the new ns_u32toa() is about 6x
    faster than Tcl_CreateHashEntry().

  * Improved performance of ns_quotehtml:

    In real-world applications (with e.g. OpenACS) this function is
    one the the most frequently used functions of NaviServer. This
    change improves the performance of "ns_quotehtml" by about 40-60% by
    avoiding per-character calls to *DStringAppend and some more

  * Improved performance of server internal "ns_set" operations: remove
    the need of 3 malloc()/free() pairs per request

  * Use Tcl_CreateHashEntry() in frequently called functions instead
    of Tcl_FindHashEntry() since the second one uses the first.

  * Avoid strlen() operations on several occasions
  * Improved speed of line-counting in .adp files by a factor of 2
  * Reduced scope of mutex lock when registering filters

Bug Fixes:

 - Allow fully qualified domain names in "Host" header field
   (as allowed in RFC 2976)

 - Avoid potential hangs of nxproxy in cases the evaltimeout is very
   small and the client is writing much data causing blocking write

 - urlencode reform:

   * The new code conforms to RFC3986 (2005) rather than RFC1738
     (1994) and RFC1808 (1995) vs. RFC2396 (1998).  The newer RFC3986
     has a more precise and differently structured definition (e.g. no
     'unwise' characters) of characters encoded in URLs. The coding of
     the query part is actually defined in the HTML 4.01
     definitions. Legacy sites can use the old encoding, when
     compiling NaviServer with RFC1738 defined.

   * Produce a warning, when a not properly encoded URL is passed to
     the location header field (e.g. ns_returnredirect). Some newer
     browsers might reject those redirects. Only characters, which
     have to be always coded, are flagged.

   * ns_urldecode: Make sure, that only valid percent codes are
     converted back.

   * ns_urledecode: Use URL encoding charset, when no charset is
     specified.  This fixes an old (at least 10 year old) bug which
     can allow to sneak in binary nulls into query variables, which
     provide a potential injection attack vector

  - ns_server active|all|queued: Better handling of querying data from
    concurrent threads. Handle now semi-parsed requests: these are not
    "queued", but not all information is available for querying it. We
    do now the best effort to report on fields that are trusted.

  - Complain on connection output operations when the connection is
    already closed. Previous version of NaviServer swallowed output
    operations on already-closed connections more or less silently,
    leading to hard to understand messages in the error log. This
    happens in particular often during error handling in OpenACS,
    where a "ad_script_obort" is missing. The new code raises now an
    error message, pointing to the erroneous code. When the old
    behavior is necessary for a while for legacy installations, the
    global configuration parameter "rejectalreadyclosedconn" can be
    set to "false".

  - Fix duplicate DriverClose calls in context of UDP drivers

  - Undo potential crlf-translations in the body of (e.g. POST)
    requests with Content-Type "www-form-urlencoded"

    Background: When a hidden form fields contains line-breaks, these
    are transformed pn transit into CRLF by current browsers in POST
    requests. In this step the value of a hidden form-field might
    become larger on every iteration, which might result in a
    quadratic growth on multiple edits of such a field. The new
    behavior undoes this effect.

  - Improved handling of requests in ancient (pre HTTP/1.0 1996) HTTP
    requests in combination with writer threads. Previous versions
    could run into a too restrictive sanity assert, assuming that
    reply headers must be always present (which is not the case in the
    rather informal HTTP versions before HTTP/1.0)

  - Virtual server fixes:
    * Avoid potential double-initialization of driver modules when
      multiple servers are used.
    * Postpone registration of virtual servers until all servers
      are defined. Before it was possible that NaviServer boot was
      terminated, when a default server of a driver was not yet defined.

  - Improved handling of binary data:
    NsTclObjIsByteArray() behaves now more like the (Tcl internal)
    TclIsPureByteArray() to figure out, when the bytearray data has
    to be treated as binary. Cases of pure and impure bytearrays
    are handled now by NaviServer.

  - Fixed old bug, were SSL_shutdown could hang (observed on Linux

  - Fixed potential crashes as flagged by fb infer.

  - Improved robustness on invalid URLs: Don't Fatal on invalid URLs
    passed to NSDriverClientOpen(), but spit out a warning.

  - Improved Tcl code safety (guarding "file delete" against names
    starting with a "--")

  - Improved handling of invalid input from config file for rolling

  - Make sure "ns_server serverdir" and "ns_server pagedir" return
    normalized paths to avoid potential confusions

  - Improved windows portability:
    * Cope with changes in Universal CRT in Visual Studio 2015 where e.g.
      vsnprintf() is no longer identical to _vsnprintf().

    * Improved Windows support for recent versions of visual studio
      (versions after visual studio 2010) by introducing NS_EINPROGRESS
      and NS_EINTR for abstracting from the raw constants (many thanks to
      Maurizio for the input)

    * Make nsproxy compile-able under windows

  - Improved Unix Portability:
    Support systems without RLIMIT_AS (e.g. OpenBSD 6.2)

  - Improved Tcl portability:

    * Starting with Tcl 8.7a1, Tcl has actually two different types
      for bytearrays, the old "tclByteArrayType" and a new
      "properByteArrayType", where both have the string name
      "bytearray".  NsTclObjIsByteArray() is now extended to handle
      both types.

Documentation improvements:

  - Updated several man pages
    * admin-maintenance.man
    * commandlist.man
    * ns_accesslog.man
    * ns_adp_exception.man
    * ns_adp_include.man
    * ns_base64decode.man
    * ns_buildsqldate.man
    * ns_cache.man
    * ns_cancel.man
    * ns_chan.man
    * ns_connchan.man
    * ns_db.man
    * ns_driver.man
    * ns_gmtime.man
    * ns_http.man
    * ns_info.man
    * ns_job.man
    * ns_locationproc.man
    * ns_parseheader.man
    * ns_perm.man
    * ns_proxy.man
    * ns_serverrootproc.man
    * ns_shutdown.man
    * ns_sockcallback.man
    * ns_urlencode.man
    * ns_write.man
    * nssock.man
    * nsssl.man
    * ns_setpriveleges.man
    * returnstatus-cmds.man
    * tcl-overview.man

  - Additional global documentation changes:
    * Added various cross references with "see also"
    * Added keywords for "global builtin" and "server builtin" to
      distinguish between per-server commands and global commands
    * Added for every module the name of the module as "keyword"
    * Make markup of options more consistent

  - Improved banner of online man pages:
    * fix markup
    * make URLs protocol agnostic
    * use new .io domain of sourceforge

  - Fixed spelling errors

Tcl API Changes:
  - ns_connchan: new option "-driver" for "ns_connchan open"
  - ns_cache_create returns now boolean to flag success
  - "ns_http queue|run" new parameter "-hostname" for handling SNI

  - Removed useless and undocumented subcommand "ns_set idelete"

   * ns_urlencode: New flag "-uppercase" added for supporting encoding
     for OAuth (RFC 5849); note that the "path" segment encoding has
     to be used to avoid coding space as "+".

   * ns_urlencode and ns_urldecode: Additional accepted value "cookies
     "for parameter "-part" to request cookie encoding/decoding.

   * ns_getcookie: New option "-all"

     Background: By using a cookie domain, it is possible that a
     cookie with a specified name is given two value, one from
     e.g. the parent domain, one from the current; this can lead to
     situation not easy to debug, when e.g. domain cookies are
     introduced from other applications in the same domain. The "-all"
     option provides infrastructure support to detect such situations
     by returning potentially multiple values for the cookie.

   * ns_connchan:
      New (experimental) subcommand
     ns_connchan listen ?-driver d? ?-server s? ?-bind? address port script
      Start a listening connection for the ns_connchan interface;
      Still missing: https variant via specifying "-driver" +
      handling of "-server" (and obtaining defaultserver)

  * sendmail.tcl
    - For the deprecated warning, do not write entire body of email to
      the log
    - Use local time (with timezone) instead of UTC in Date header

C API Changes:

  - New API function NsConnRequire() to make handling of required
    connections and error results more regular.

  - New API functions
      . Ns_CookieEncode()
      . Ns_CookieDecode()
      . NSDriverSockNew(): create an initialized driver Sock structure
      . Ns_HttpMessageParse(): parse a (potentially incomplete) HTTP message
      . ns_uint32toa()
      . ns_uint64toa(): substantially faster conversion of integer to
    string than snprintf()

  - Ns_SockListenCallback: alter interface to (a) return the listening
    socket and (b) to be able to bind to the fresh socket. This is
    necessary for e.g. FTPD's passive server connections, where the
    server offers a freshly bound socket to the client to connect to
    (EPSV, extended passive mode).

  - driver.c:
    * Factored out LookupDriver() to improve reusability
    * New function NSDriverSockNew() to create an initialized driver Sock

Configuration Changes:

  - Removed need to specify port for virtual servers in */servers
    section of the config files

  - Extended sample config files:

    * nsd-config.tcl
       . Added new nslog parameter "logthreadname"
       . Added new global parameter "rejectalreadyclosedconn"

    * openacs-config.tcl
       . Added configuration for strict-transport-security (HSTS)
       . Added setting of OpenACS kernel parameter
     "NsShutdownWithNonZeroExitCode" in sample config file
       . Eased configuration for nsssl by simply setting https port
       . Added new nslog parameter "logthreadname"
       . Added new global parameter "rejectalreadyclosedconn"

    * sample-config.tcl:
       . Included global parameter "shutdowntimeout" in sample config file
       . Included parameter "allowstaticresources" in nscgi section
       . Added new nslog parameter "logthreadname"
       . Added new global parameter "rejectalreadyclosedconn"
       . Make it work out-of-the box

Code Changes:

  - Reworked TLS cleanup (necessary for Tcl 8.7 and beyond); align it
    to the Tcl practices. Add compatibility for Tcl 8.5- w.r.t
    notifier-thread init after the fork.

  - Switched to the recommended way of creating detached threads via
    pthread attribute (see e.g.

 - Extended regression tests
    * ns_urlspace.test
    * cookies.test
    * ns_sls.test
    * tclresp.test
    * ns_urlencode.test
    * url2file.test
    * ns_conn_host.test
    * ns_pagepath.test
    * ns_serverpath.test
    * ns_server.test
    * ns_proxy.test
    * http.test

  - Code cleanup
    * Added const declarations
    * Added missing prototypes
    * Don't shadow local variables
    * Marked unused arguments as UNUSED
    * Removed old-style function definitions
    * Reduced implicit conversions (gcc7)
    * Prefer type use "bool" over "int" when applicable
    * Don't pass implementation-defined NULL after the last typed
      argument to a variadic function
    * Reduced number of return statements before end of function
    * Reduced variable scopes
    * Reduced size of huge switch statements

  - API modernization
    * Prefer usage of Ns_ParseObjv over manual parsing and
      error message generation
    * Prefer Tcl_SetObjResult over Tcl_SetResult
    * Stop using readdir_r() unless it is forced via compile flag
    * Aligned casts to Tcl 8.6
    * Replaced all occurrences of strcpy() with more safe variants
    * Replaced all occurrences of sprintf()  by snprintf()
      to protect better against buffer overflows
    * Prefer void* over legacy caddr_r
    * Replaced all calls to deprecated function Tcl_DStringTrunc()
      by Tcl_DStringSetLength()

 - Implemented deprecated commands as Tcl proc and complain on
   its usage
      . ns_puts
      . ns_returnadminnotice
      . env

 - Silenced static checker

 - Add CFLAGSs for convenient testing/cleanup

 - Improved error messages


 - nsstats:
    . Format values in human readable form (unless &raw=1 is used in query)
    . Return proxy stats form all proxy pools
    . Include expire date of certificate in "process" page
    . Added "commit" and "rollback" to cache statistics

 - nsdbpg:
    . Report more detailed version numbers used during built process
    . Honor "logsqlerrors" settings from config file
    . Truncate SQL statement in error.log to 10.000 chars if longer
      (to avoid overlong log file entries)
    . Improved backwards compatibility with ancient versions of PostgreSQL

 - nsimap:
    . Added optional flag "-novalidatecert" to "ns_imap open"
      (name from the flag of the IMAP implementation)
    . Use NaviServer built-in config check for SSL libraries and use same libraries
    . Updated to meet engineering standards

 - revproxy:
    . Added url_rewrite_callback
    . Added timeout handling
    . Added support for handling requests with provided content (POST, PUT, ...)
    . Generalized error handling, reverse callback registration as suggested by David.
    . Close connection explicitly after every request

 - letsencrypt:
    . New module for managing server certificates via Let's Encrypt

 - websocket:
    . Varius small updates (links, spelling errors, ...)

以上就是這次 NaviServer 4.99.16 的更新。

我有寫一份 openSUSE rpm spec for NaviServer,這是配合我自己的需求寫的,但是我不知道實務上其它人會怎麼做(囧)。

如果你要使用 revproxy 或者是 websocket module,需要 Next Scripting Framework (NSF)。我也寫了一份openSUSE RPM spec for Next Scripting Framework (NSF),這份 spec 是參考其它的 Tcl package RPM spec 以後做出來的。


Tcl/Tk 8.6.8 RELEASED

Tcl/Tk 8.6.8 RELEASED

Summary of Changes since Tcl/Tk 8.6.7:

This is a patch release, so it primarily includes bug fixes and corrections to erratic behavior. Highlighted changes are noted below. The changes file at the root of the source tree contains a more complete list. The Timelines of all changes are online.


* [TIP 477] nmake build system reform
* Support for backrefs in [array names -regexp]
* [info * methods] includes mixins
* [package prefer stable] failing case
* More robust [load] for ReactOS
* Fix crashes or hangs in...
- traced namespace teardown
- method cloning, oo-15.15
- hash table overflow 32 bits
- ensemble configure -map $x -subcommands $x
- memory exhaustion, imgPhoto-18.*
- photo image copy to self
- [scale] set with a bignum value

* Fix memory leaks in...
- [text] B-tree operations
- photo image operations
- Tk platform code for macOS 10.13

* Canvas closed polylines fully honor -joinstyle
* Fix coords rounding when drawing canvas items
* Linux fontchooser sync with available fonts
* Canvas rotated text overlap detection
* Display of Long non-wrapped lines in text
* Drop support for macOS 10.5
* Tk improvements on macOS...
- Implement [wm_iconphoto]
- colorspace improvement
- scrolling issues
- clipping regions in scrolling and drawing
- redraw artifacts
- responsive menu bar for command line apps
- support png from mac screenshots
- file selector "all types" setting
- fixes for [raise]
- support of menu -postcommand
- enable custom icon display
- fix bind failures
- make [tk busy -cursor] a silent no-op
- [wm withdraw] on Window and Dock menus

* Updated bundled packages
- Itcl 4.1.1 (incompatible with Itk 4.0, get Itk 4.1)
- sqlite3 3.21.0
- Thread 2.8.2
- tdbc* 1.0.6
- http 2.8.12

以上就是這次版本 8.6.8 的更新。


JSON Web Token and Base64Url

我在讀維基百科的條目也有跟著使用 Tcl 驗證,我卡住了一段時間,因為最後的 Token 跟我程式計算出來的不同,不過最後我發現是 base64 和 Base64Url 有一些小小的不同,最後在搜尋 Tcler's wiki 以後知道要怎麼做了,下面是測試程式:

package require sha256

proc base64url_encode {string} {
    tailcall string map {+ - / _ = {}} [binary encode base64 $string]

proc base64url_decode {string} {
    tailcall binary decode base64 [string map {- + _ /} $string]

set header {{"alg":"HS256","typ":"JWT"}}
set payload {{"loggedInAs":"admin","iat":1422779638}}
set key {secretkey}

set unsignedToken "[base64url_encode $header].[base64url_encode $payload]"
set signature [base64url_encode [::sha2::hmac -bin -key $key $unsignedToken]]
set token "$unsignedToken.$signature"
puts $token


小知識: base64 != base64Url

* 更新:加入 decode 的部份


Apache Rivet: status code and content type

最近在學習 Vue.js,所以有做一些 server 端的測試。寫法大致上是這樣:

Vue.js + Axios  -> send POST request to server -> get response

也就是所謂的前後端分離。在這個情況下,server 端(例如 Apache HTTP server + Apache Rivet 的組合)就變成比較像 web API 的部份。

XML 的範例可以看 Apache Rivet 的手冊:
# Ajax query servelet: a pseudo database is built into the dictionary 'composers' with the
# purpose of emulating the role of a real data source. 
# The script answers with  2 types of responses: a catalog of the record ids and a database 
# entry matching a given rec_id. The script obviously misses the error handling and the
# likes. Just an example to see rivet sending xml data to a browser. The full Tcl, JavaScript
# and HTML code are available from http://people.apache.org/~mxmanghi/rivet-ajax.tar.gz

# This example requires Tcl8.5 or Tcl8.4 with package 'dict' 
# (http://pascal.scheffers.net/software/tclDict-8.5.2.tar.gz)

# A pseudo database. rec_id matches a record in the db

set composers [dict create  \
                1 {first_name Claudio middle_name "" last_name Monteverdi   \
                    lifespan 1567-1643 era Renaissance/Baroque}             \
                2 {first_name Johann middle_name Sebastian last_name Bach   \
                    lifespan 1685-1750 era Baroque }                        \
                3 {first_name Ludwig middle_name "" last_name "van Beethoven" \
                    lifespan 1770-1827 era Classical/Romantic}              \
                4 {first_name Wolfgang middle_name Amadeus last_name Mozart \
                    lifespan 1756-1791 era Classical }                      \
                5 {first_name Robert middle_name "" last_name Schumann      \
                    lifespan 1810-1856 era Romantic} ]

# we use the 'load' argument in order to determine the type of query
# load=catalog:         we have to return a list of the names in the database
# load=composer&amp;res_id=<id>: the script is supposed to return the record
#               having <id> as record id

if {[::rivet::var exists load]} {

# the xml declaration is common to every message (error messages included)

    set xml "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
    switch [::rivet::var get load] {
        catalog {
            append xml "<catalog>\n"
            foreach nm [dict keys $composers] {
                set first_name  [dict get $composers $nm first_name]
                set middle_name [dict get $composers $nm middle_name]
                set last_name   [dict get $composers $nm last_name]
                append xml "    <composer key=\"$nm\">$first_name "
                if {[string length [string trim $middle_name]] > 0} {
                    append xml "$middle_name "
                append xml "$last_name</composer>\n"
            append xml "</catalog>\n"
        composer {
            append xml "<composer>\n"
            if {[::rivet::var exists rec_id]} {
                set rec_id [::rivet::var get rec_id]
                if {[dict exists $composers $rec_id]} {
                    foreach {k v} [dict get $composers $rec_id] {
                        append xml "<$k>$v</$k>\n"
            append xml "</composer>\n"

# we have to tell the client this is an XML message. Failing to do so
# would result in an XMLResponse property set to null

    ::rivet::headers type "text/xml"
    ::rivet::headers add Content-Length [string length $xml]
    puts $xml

只要知道怎麼拿取 request 的資料跟如何回應,就可以組成 web API。下面是我快速驗證的 prototype:
    package require rl_json
    package require tdbc::postgres

    ::rivet::load_response response

    if {[info exists response(sqlstring)]==1} {
        set sqlstring $response(sqlstring)

    if {[info exists response(sqlstring)]==0 || [info exists sqlstring]==0} {
        ::rivet::headers  numeric 400
    } else {
        ::rivet::headers numeric 200
        ::rivet::headers type application/json

        tdbc::postgres::connection create db -user danilo -password danilo -port 5432 -database danilo

        set stmt [db prepare $sqlstring]
        set resultset [$stmt execute]

        set cols [$resultset columns]
        set param_colitem [list]
        foreach col $cols {
            lappend param_colitem "string \"$col\""
        rl_json::json set data "column" [rl_json::json new "array" {*}$param_colitem]

        set param_rowitems [list]
        $resultset foreach -as lists result {
            set param_rowitem [list]
            lappend param_rowitem "array"
            foreach row $result {
                lappend param_rowitem "string \"$row\""

            lappend param_rowitems $param_rowitem

        $resultset close
        $stmt close
        db close

        set data [rl_json::json set data "row" [rl_json::json new "array" {*}$param_rowitems]]
        puts $data

Apache Rivet 有自己的 Database API,可是因為我只是要寫一個測試用的程式,所以直接使用 TDBC。這個範例是送出 SQL statement,然後 POST 到 server 上,server 執行以後回傳結果。

因為並沒有做使用者資料驗證和檢查,這個測試程式請不要在測試目的以外的地方使用,這只是用來說明,我們可以使用 Apache Rivet 用來實作 web api。


NaviServer: enable gzip compression

How to enable gzip compression

NaviServer 在 nsd-config.tcl 中的 ns/server/${server} 加入下列的設定,動態網頁 ADP 的部份就可以支援 gzip compression:
ns_section     "ns/server/default"
# Compress response character data: ns_return, ADP etc.
ns_param        compressenable  on      ;# false, use "ns_conn compress" to override
ns_param        compresslevel   4       ;# 4, 1-9 where 9 is high compression, high overhead
ns_param        compressminsize 512     ;# Compress responses larger than this
# ns_param      compresspreinit true    ;# false, if true then initialize and allocate buffers at startup

一開始的時候我以為和 AOLServer 一樣,需要 nszlib module,但是我寫好了 RPM spec 並且安裝好了以後,才發現 NaviServer 已經整合了 zlib 的部份,所以在一開始 configure 的時候就需要 --with-zlib 設定才行(我是往前翻安裝的筆記並且參考連結以後才發現不用安裝 nszlib module)。

經過實測,NaviServer 只要針對 ADP 的部份進行 gzip compression,而靜態網頁看起來是沒有壓縮的(PS. 我只有加這篇文章的設定進行測試)。


NaviServer: 設定 HTTPS (self-signed certificate)

nsssl module 是 NaviServer 用來處理 SSL 部份的 module。我目前使用的 NaviServer 4.99.15 已經整合 nsssl 的功能進入核心。

下面是參考 nsssl 文件上如何產生  self-signed certificate 與我之前其它 server 的設定,綜合之後的筆記。

Creating self-signed certificate,首先是建立 localhost.conf 設定檔案:
default_bits       = 2048
default_keyfile    = localhost.key
distinguished_name = req_distinguished_name
req_extensions     = req_ext
x509_extensions    = v3_ca

countryName                 = Country Name (2 letter code)
countryName_default         = US
stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = New York
localityName                = Locality Name (eg, city)
localityName_default        = Rochester
organizationName            = Organization Name (eg, company)
organizationName_default    = localhost
organizationalUnitName      = organizationalunit
organizationalUnitName_default = Development
commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_default          = localhost
commonName_max              = 64

subjectAltName = @alt_names

subjectAltName = @alt_names

DNS.1   = localhost
DNS.2   =

Run the following commands using OpenSSL to create a self-signed certificate in Linux or Mac OSX with OpenSSL :
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -config localhost.conf -passin pass:YourSecurePassword

cat localhost.crt localhost.key > server.pem
rm -rf localhost.crt localhost.key
openssl dhparam 2048 >> server.pem

然後將 server.pem 複製到 /var/lib/naviserver/modules/nsssl 目錄下。
接下來設定 NaviServer 的部份(修改 conf 目錄下的 nsd-config.tcl,或者是你的設定檔):

ns_section    ns/server/default/modules
ns_param      nsssl                nsssl.so

ns_section    ns/server/default/module/nsssl
ns_param      certificate      $home/modules/nsssl/server.pem
ns_param      address
ns_param      port             8081
ns_param      protocols            "!SSLv2:!SSLv3"
ns_param      verify                0

ns_param      extraheaders {
   Strict-Transport-Security "max-age=31536000; includeSubDomains"
   X-Frame-Options SAMEORIGIN
   X-Content-Type-Options nosniff

原本範例 port 是設定為 443,不過因為 1024 以下的 port 需要 root 權限,所以我改成 8081 來進行測試。

注意:default 要視你的設定而定,因為我是使用一開始的 nsd-config.tcl 來測試,所以 server name 是 default。


Next Scripting Framework

Next Scripting Framework

NX is a highly flexible, Tcl-based, object-oriented scripting language. It is a descendant of XOTcl and was designed based on 10 years of experience with XOTcl in projects containing several hundred thousand lines of code.

上面是引述官網的介紹,也就是 NSF 是 XOTcl 的後繼者。在我的認知中,以前 Tcl 最主要提供物件導向支援的套件,一個是 Incr Tcl,一個就是 XOTcl。

我並不是 NSF 的使用者,不過有需要研究一下要怎麼安裝在 openSUSE 才對,然後我會試寫一個 RPM spec 來安裝。

因為 NaviServer 的 WebSocket 實作是以 NSF 寫的,所以如果我要知道目前 NaviServer 的實作情況,那麼先置條件是要裝 NSF。等裝完並且沒有問題以後,才能夠開始 NaviServer 的 WebSocket 功能測試。

配合維基百科上的 Handshake 說明:

GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/

安裝 NSF 成功。
接下來執行 chat 範例成功(需要設定 host 為 localhost)。

更新 2017/11/25
我發現 revproxy module (Reverse Proxy module) 也是使用 NSF 寫的,所以 NaviServer 也有提供 Reverse Proxy 的功能,裝上 Reverse Proxy module 就好了。

tcl-lmdb v0.3.6


tcl-lmdb - Tcl interface to the Lightning Memory-Mapped Database


This is the Lightning Memory-Mapped Database (LMDB) extension for Tcl using the Tcl Extension Architecture (TEA).

LMDB is a Btree-based database management library with an API similar to BerkeleyDB. The library is thread-aware and supports concurrent read/write access from multiple processes and threads. The DB structure is multi-versioned, and data pages use a copy-on-write strategy, which also provides resistance to corruption and eliminates the need for any recovery procedures. The database is exposed in a memory map, requiring no page cache layer of its own. This extension provides an easy to use interface for accessing LMDB database files from Tcl.

Main Change

  • Fix Segfault when you use an env without opening it


merge gahr 的 solution。


Install NaviServer on openSUSE

NaviServer is a high performance web server written in C and Tcl.

./configure --prefix=/var/lib/naviserver --with-tcl=/usr/lib64 --with-zlib=/usr/lib64 --with-openssl=/usr/lib64
sudo make install

如果在 make 的時候遇到 -fstack-protector-strong 或者是 -fstack-clash-protection 無法辨識的問題, 修改 include/Makefile.global, 將 -fstack-protector-strong 或者是 -fstack-clash-protection 改為 -fstack-protector。 這是編譯套件時 GCC 新舊版本不同所造成的狀況。
sed -i s/stack-protector-strong/stack-protector/g include/Makefile.global
sed -i s/stack-clash-protection/stack-protector/g include/Makefile.global

NaviServer 預設的使用者是 nsadmin,所以需要使用下列的指令來增加使用者:
sudo useradd -d/var/lib/naviserver -U -M -s/bin/bash nsadmin

再來需要修改 NaviServer 目錄的擁用者設定,修改擁有者 user:group 為 nsadmin:nsadmin。
sudo chown -R nsadmin:nsadmin /var/lib/naviserver

再來要設定 nsadmin 帳號的密碼,使用 passwd 改變 nsadmin 使用者的密碼:
sudo passwd nsadmin

再來是執行 NaviServer
sudo -i -u nsadmin  /var/lib/naviserver/bin/nsd -f -t /var/lib/naviserver/conf/nsd-config.tcl

再來瀏覽 http://localhost:8080/,如果出現 NaviServer 的訊息就表示成功了。

/var/lib/naviserver/conf 下還有其它的設定範例,包含 openacs-config.tcl, sample-config.tcl, simple-config.tcl, 也可以用來參考。

以上手動安裝的部份。在知道怎麼手動安裝以後,我就寫了 script 來產生 RPM 檔案,成果請看 naviserver-spec

(更新:我發現和裝到 /usr/local/ns 的狀況不同,所以不用設定 log 目錄位置,所以我移除了怎麼設定 log 目錄的部份)


Wub systemd service

假設  Wub 目錄是放在 /var/opt/wub,將 user:group 設定為 wwwrun:www,所以試寫第一版的 wub.service 如下:

Description=Tcl Wub web-server

ExecStart=/usr/bin/tclsh Wub.tcl
# Restart service after 10 seconds if Wub service crashes


如果是 openSUSE,檔名是 wub.service,然後放在 /etc/systemd/system 目錄下。這樣就可以將 wub 作為一個系統服務來管理。

也就是說,可以這樣啟動 Wub:
sudo service wub start

關閉 Wub:
sudo service wub stop

sudo service wub status

更新 2017/11/22:
加入 After= 和 WantedBy=


TclVFS @ core.tcl.tk

TclVFS 目前主要開發的位址已經被移到 core.tcl.tk,所以如果需要拿取最新原始碼或者是對這個套件有興趣的人需要更新網址,不過看起來還在整理的階投。還有就是我注意 mpexpr 也被放到 core.tcl.tk 的列表中。

如果 TclVFS 有人繼續維護的話,我想是一件好事,因為在一些情況下, Tcl Virtual Filesystem 是一個很好用的工具。


Apache 2, Apache Rivet and openSUSE

這篇假設你已經在 openSUSE 安裝了 Tcl,如果沒有,那需要先安裝才行。測試的環境為 openSUSE Leap 42.3。

Apache HTTP Server 2 在 openSUSE 安裝的方式:
sudo zypper in apache2

如果要啟動 Apache 2:
sudo systemctl start apache2

如果要停止 Apache 2:
sudo systemctl stop apache2

sudo chkconfig apache2 on

sudo chkconfig apache2 off

如果需要公開在網路上,還需要設定防火牆,否則只能在 localhost 使用。

再來安裝 Apache Rivet,如果要在 openSUSE 安裝,先設定軟體庫:
sudo zypper addrepo https://download.opensuse.org/repositories/Apache:/Modules/openSUSE_Leap_42.3/ Apache-Modules

sudo zypper refresh

sudo zypper install apache2-mod_rivet

加入 Apache Rivet module 到 Apache 2:
sudo a2enmod rivet
(* 如果要移除,使用 sudo a2dismod rivet 來移除)

接下來重新啟動 Apache 2:
sudo systemctl restart apache2

到 /srv/www/htdocs/ 目錄下,建立 hello.rvt,內容如下:
<? set hello_message "Hello world" ?>
    <title><?= $hello_message ?></title>
  <body><?= [::rivet::html $hello_message pre b] ?></body>
瀏覽 http://localhost/hello.rvt,如果有看到訊息表示成功安裝。

再來設定 Apache2 HTTPS 的部份。一開始先確定 mod_ssl 有開啟:
sudo a2enmod ssl

再來是設定 Self-Signed Certificates 的部份,在 Linux 上使用 OpenSSL 建立。Create a config file for your certificate :
default_bits       = 2048
default_keyfile    = localhost.key
distinguished_name = req_distinguished_name
req_extensions     = req_ext
x509_extensions    = v3_ca

countryName                 = Country Name (2 letter code)
countryName_default         = US
stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = New York
localityName                = Locality Name (eg, city)
localityName_default        = Rochester
organizationName            = Organization Name (eg, company)
organizationName_default    = localhost
organizationalUnitName      = organizationalunit
organizationalUnitName_default = Development
commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_default          = localhost
commonName_max              = 64

subjectAltName = @alt_names

subjectAltName = @alt_names

DNS.1   = localhost
DNS.2   =
Run the following 2 commands using OpenSSL to create a self-signed certificate in openSUSE with OpenSSL :
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -config localhost.conf -passin pass:YourSecurePassword

sudo openssl pkcs12 -export -out localhost.pfx -inkey localhost.key -in localhost.crt

然後將我們製造的檔案複製到 Apache 的目錄下:
sudo cp localhost.crt /etc/apache2/ssl.crt/server.crt
sudo cp localhost.key /etc/apache2/ssl.key/server.key

如果是 Officially Signed Certificate,可以參考 OpenSUSE: Setting Up a Secure Web Server with SSL

再來修改 /etc/sysconfig/apache2 的設定,

複製 /etc/apache2/vhost.d/vhost-ssl.template 到 /etc/apache2/vhost.d/vhost-ssl.conf,主要的設定如下:
<IfDefine SSL>
<IfDefine !NOSSL>

## SSL Virtual Host Context

<VirtualHost *:443>

    #  General setup for the virtual host
    DocumentRoot "/srv/www/htdocs"
    #ServerName www.example.com:443
    #ServerAdmin webmaster@example.com
    ErrorLog /var/log/apache2/error_log
    TransferLog /var/log/apache2/access_log

    #   SSL Engine Switch:
    #   Enable/Disable SSL for this virtual host.
    SSLEngine on

    #   You can use per vhost certificates if SNI is supported.
    SSLCertificateFile /etc/apache2/ssl.crt/server.crt
    SSLCertificateKeyFile /etc/apache2/ssl.key/server.key
    #SSLCertificateChainFile /etc/apache2/ssl.crt/vhost-example-chain.crt

    #   Per-Server Logging:
    #   The home of a custom SSL log file. Use this when you want a
    #   compact non-error SSL logfile on a virtual host basis.
    CustomLog /var/log/apache2/ssl_request_log   ssl_combined



接下來讓 main site 使用 HTTPS,編輯 /etc/apache2/default-server.conf,加入下面的設定:
IncludeOptional /etc/apache2/conf.d/*.conf
IncludeOptional /etc/apache2/vhosts.d/*.conf

接下來重新開啟 Apache 2,
sudo systemctl restart apache2

瀏覽 https://localhost/hello.rvt, 如果有看到訊息表示成功安裝(PS. 因為是 Self-Signed Certificates,所以 Firefox 會看到警告訊息)。

若要強制使用 SSL,需要開啟 mod_rewrite 才行。
sudo a2enmod rewrite

再來修改 /etc/apache2/vhost.d/vhost-ssl.conf
<VirtualHost *:80>
    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteRule (.*) https://%{SERVER_NAME}/$1 [R,L] 

<VirtualHost *:443>

    #  General setup for the virtual host
    DocumentRoot "/srv/www/htdocs"
    #ServerName www.example.com:443
    #ServerAdmin webmaster@example.com
    ErrorLog /var/log/apache2/error_log
    TransferLog /var/log/apache2/access_log

    #   SSL Engine Switch:
    #   Enable/Disable SSL for this virtual host.
    SSLEngine on

    #   You can use per vhost certificates if SNI is supported.
    SSLCertificateFile /etc/apache2/ssl.crt/server.crt
    SSLCertificateKeyFile /etc/apache2/ssl.key/server.key
    #SSLCertificateChainFile /etc/apache2/ssl.crt/vhost-example-chain.crt

    #   Per-Server Logging:
    #   The home of a custom SSL log file. Use this when you want a
    #   compact non-error SSL logfile on a virtual host basis.
    CustomLog /var/log/apache2/ssl_request_log   ssl_combined


* 2017/11/18 更新:更新 rewrite 的規則

接下來重新開啟 Apache 2,
sudo systemctl restart apache2

這樣就會強制都使用 HTTPS 瀏覽。


TemplaTcl and 8.6.1

TemplaTcl: a Tcl template engine

在 Tcler'wiki 發現的套件,我在測試以後發現可以使用,並且對下面的使用留言感到疑惑,

I'm trying to run this script and keep getting an error for the following line of script:
$obj($self:interp) eval $tclBuf

經過實測,發現 Windows 7 (64bit) 的 8.6.7 和 openSUSE LEAP 42.3 的  8.6.7 可以正常使用,但是 Ubuntu 14.04 的 8.6.1 會發生同樣的錯誤訊息。

但是 Ubuntu 環境,使用 ActiveTcl 8.6.3 也同樣可以執行,所以目前推定是 Tcl 8.6.1 應該有問題,會導致 TemplaTcl 無法執行。

在更多測試以後,發現一個很有趣的問題。就是執行 render 之後,內容是會累積的。這在 CGI 程式下有可能沒問題(因為 process 都會再重新執行一次),但是如果是要使用一個像 Tanzer 的 http server,就有可能會出現問題。

(* Tanzer 有自己的 template system,但是因為我是要測試 TemplaTcl,所以拿 TemplaTcl 來用)

        if {$obj($self:options:printCommand) == "_defaultSpool"} {
            set x [$obj($self:interp) eval {_defaultSpool {} get}]
            $obj($self:interp) eval {_defaultSpool {} clear}

            set obj($self:data) {}
            return $x

而這樣才能夠在 Tanzer 每次 request 時正常的顯示,而不會資料重複。

如果還要修改,再來就是針對小型網頁的優化,就是先讀取檔案內容並且儲存到 dict,然後每次我們需要的時候再從 dict 拿取。 PS. 但是這樣不知道多人環境下會不會有問題

這樣就可以由 Tanzer 作 URL routing,並且撰寫需要動作的部份,需要輸出畫面時,就由 TemplaTcl 來輸出畫面。


openSUSE docker image for Tcl

openSUSE 有內建 docker,如果要安裝:
sudo zypper install docker

安裝以後,首先是先確定 Docker server 有在執行:
sudo systemctl status docker

如果沒有,需要啟動 Docker server:
sudo systemctl start docker

再來是下載 openSUSE LEAP 42.3 影像檔,
sudo docker pull opensuse:42.3

sudo docker images

如果要執行 openSUSE LEAP 42.3 image,
sudo docker run -t -i opensuse:42.3 /bin/bash

touch Dockerfile

接下來加入我們 Dockerfile 的內容:
FROM opensuse:42.3
MAINTAINER Danilo Chang 
RUN zypper addrepo http://download.opensuse.org/repositories/devel:/languages:/tcl/openSUSE_Leap_42.3/  tcl-Leap_42.3 && zypper addrepo https://download.opensuse.org/repositories/home:/danilochang/openSUSE_Leap_42.3/ danilo
RUN zypper --no-gpg-checks refresh && zypper --no-gpg-checks -n update
RUN zypper --no-gpg-checks -n install tcl tcllib tclvfs vectcl tls libgumbo1 tdom rl_json  tcl-trf tclcurl tclreadline memchan tcl-sugar tclsoap tclws                                                                          
COPY tclshrc /root/.tclshrc                                                                                     
CMD ["tclsh8.6"]

CMD ["tclsh8.6"] 讓我們如果不指定 /bin/bash,執行的時候就會直接進入 tclsh 的環境。需要注意的是,我從 tcl devel 和我自己的 repositories 下載檔案。

tclshrc 的內容:
if {$tcl_interactive} {
    package require tclreadline
    proc ::tclreadline::prompt1 {} {
        global env
        if {[catch {set pwd [pwd]} tmp]} {
            set pwd "unable to get pwd"

        if [info exists env(HOME)] {
            regsub $env(HOME) $pwd "~" pwd

        if [info exists env(USER)] {
            set user $env(USER)
        } else {
            set user tclsh

        return "$user@[lindex [split [info hostname] "."] 0]:$pwd% "

再來使用 docker build 建立影像檔:
sudo docker build -t="ray2501/docker-tcl:devel" .

sudo docker run -t -i ray2501/docker-tcl:devel /bin/bash

執行的時候如果不指定執行 bash,就會直接執行 tclsh8.6:
sudo docker run -t -i ray2501/docker-tcl:devel

如果要存取本地的目錄並且對應到 docker 中的目錄,並且先執行 bash:
sudo docker run -t -i -v /home/danilo/Public/tclfiles:/root ray2501/docker-tcl:devel /bin/bash

這樣如果執行 /bin/bash 以後,可以在 bash 環境中使用 tclsh8.6 來執行 /home/danilo/Public/tclfiles 中的 tcl script 檔案(在 docker 中的目錄則為 /root)。

(* 或者是將 Tcl script 檔案使用 COPY 複製到 docker 影像檔以後,使用 CMD 來執行。總之,應該有更多的設定方法才對。)

如果要停止容器,使用下列的方式停止 (找出 CONTAINER-ID 以後使用 stop 停止):
sudo docker stop $(sudo docker ps -a -q)

sudo docker rm $(sudo docker ps -a -q)

如果要刪除 Docker image,先列出目前的 image:
sudo docker images

sudo docker rmi ray2501/docker-tcl:devel

這樣就完成了一次 openSUSE Tcl docker 環境的基本測試。如果需要參考更多的 Dockerfile,可以參考 docker-tcl

在建立一個基本的 Tcl 環境可以使用以後,再來建立一個 Wub server, 在我們的工作目錄下放置 wub 目錄和相關的檔案,再修改 wub/Wub/wub.vcl 的設定,
backend default {
    set backend.host = "";
    set backend.port = "8080";

將 backend.host 修改為 docker 影像檔的網路設定值。再更新我們的 Dockerfile:
FROM opensuse:42.3
RUN zypper addrepo http://download.opensuse.org/repositories/devel:/languages:/tcl/openSUSE_Leap_42.3/  tcl-Leap_42.3 && zypper addrepo https://download.opensuse.org/repositories/home:/danilochang/openSUSE_Leap_42.3/ danilo
RUN zypper --no-gpg-checks refresh && zypper --no-gpg-checks -n update
RUN zypper --no-gpg-checks -n install tcl tcllib tclvfs vectcl tls libgumbo1 tdom rl_json  tcl-trf tclcurl tclreadline memchan tcl-sugar tclsoap tclws which
COPY tclshrc /root/.tclshrc
COPY wub /root/wub
WORKDIR /root/wub
CMD ["tclsh8.6", "Wub.tcl"]

在工作目錄下新增一個檔案 .dockerignore,內容如下:

再來使用 docker build 建立影像檔:
sudo docker build -t="ray2501/docker-wub:devel" .

如果建立成功,使用下面的方式執行(將本機的 8080 port 對應到 docker 容器的 8080 port):
sudo docker run -p 8080:8080 -d ray2501/docker-wub:devel

sudo docker ps -l

sudo docker run -t -i ray2501/docker-wub:devel /bin/bash

curl -i localhost:8080

或者是開啟瀏覽器瀏覽 http://localhost:8080

Docker and Tcl


這只是先做資訊收集的工作。搜尋以後,Tcler'wiki 上有關於 docker 的條目。所以目前已經有的項目是 Ubuntu-based batteries-included Tcl 8.6 for Docker

Docker —— 從入門到實踐
Open source Java projects: Docker
How to access a directory in hosts machine from inside a docker container?
Docker 無法移除死掉的 Container 修復

(* openSUSE 有內建,所以先確定是否已經有安裝,如果沒有,
使用 sudo zypper install docker 安裝)


openSUSE container:

Tcl 相關連結:

Docker 相關連結:
Moby project

Docker + Kubernetes
(但是這通常是使用 Docker 一段時間後有調度需求,所以開始使用 Kubernetes)

那麼可以建立一個基本的 Tcl 環境,指定本地環境的目錄作為 volume,然後在 docker 執行我想要測試的 tcl script 嗎……? 目前的第一步。


List tm file

package require fileutil

set tmpaths [::tcl::tm::path list]
foreach tmpath $tmpaths {
    if {[file exists $tmpath] && [file isdirectory $tmpath]} {
        foreach file [fileutil::find $tmpath {string match -nocase *.tm}] {
            puts $file

這只是練習用的程式。使用 tcllib fileutil,利用 fileutil::find 尋找副檔名為 tm 的檔案。雖然我有寫第一行,不過目前只有在 Windows 7 測試過。

Eclipse Dynamic Languages Toolkit

還不確定我會不會使用 Eclipse 一段時間,但是記錄一下 Eclipse 對於 Tcl 的支援。支援的能力來自於 Eclipse Dynamic Languages Toolkit (DLTK),需要自己安裝 plug-in

DLTK 有支援 Tcl Syntax highlighting。
  • 根據 Which tools support Java 9’s new modularity features 的資訊,目前並沒有所有的工具都準備好迎接 Java 9 的模組系統。隨著 Java 9 的正式發佈,比較大量的測試才正開始而已。
  • 但是 Eclipse 和 Apache Maven,也就是接下來我準備要測試的工具已經準備好迎接 Java 9 的模組化了。
  • 在升級到 JDK 9 以後,我安裝用來測試的 Eclipse 和 Apache Maven 可以正確運作。接下來測試 DLTK (Tcl),看起來是正常的,不過實際上要等更多的使用情況才知道。
另外,就 openSUSE: tclBlend and OpenJDK 9 注意到的,JDK 9 的目錄架構有所改變,看起來是因為 openSUSE drop 掉三十二位元的支援,所以可以進行目錄簡化的工作。但是還不確定這是否會成為通例。(PS. JRE_HOME 需要注意是否有正確設定,至少我的沒有)

JDK and JRE File Structure (JDK 8)
Installed Directory Structure of JDK and JRE (JDK 9)


openSUSE: tclBlend and OpenJDK 9

使用 zypper addrepo 的方式增加 Java 的 Repository,然後升級到 OpenJDK 9(警語:這樣有可能會讓系統不穩定,所以除非需要才這樣做),但是沒有 Java plugin。

OpenJDK 9 的目錄架構略有改變,所以編譯 tclBlend 會失敗。經過查看 openSUSE 的目錄架構,按照錯誤提示,修改 tcljava.m4,下面是 github 上的修改記錄:

+        # OpenJDK 9 Linux (server JVM)
+        F=lib/libjava.so
+        if test "x$ac_java_jvm_jni_lib_flags" = "x" ; then
+            AC_MSG_LOG([Looking for $ac_java_jvm_dir/$F], 1)
+            if test -f $ac_java_jvm_dir/$F ; then
+                AC_MSG_LOG([Found $ac_java_jvm_dir/$F], 1)
+                D=`dirname $ac_java_jvm_dir/$F`
+                ac_java_jvm_jni_lib_runtime_path=$D
+                ac_java_jvm_jni_lib_flags="-L$D -ljava -lverify"
+                D=$ac_java_jvm_dir/lib/server
+                ac_java_jvm_jni_lib_runtime_path="${ac_java_jvm_jni_lib_runtime_path}:$D"
+                ac_java_jvm_jni_lib_flags="$ac_java_jvm_jni_lib_flags -L$D -ljvm"
+            fi
+        fi

然後使用 autoconf 更新設定,下面是 github 上 configure 的修改記錄:
+        # OpenJDK 9 Linux (server JVM)
+        F=lib/libjava.so
+        if test "x$ac_java_jvm_jni_lib_flags" = "x" ; then
+    echo Looking for $ac_java_jvm_dir/$F >&5
+            if test -f $ac_java_jvm_dir/$F ; then
+    echo Found $ac_java_jvm_dir/$F >&5
+                D=`dirname $ac_java_jvm_dir/$F`
+                ac_java_jvm_jni_lib_runtime_path=$D
+                ac_java_jvm_jni_lib_flags="-L$D -ljava -lverify"
+                D=$ac_java_jvm_dir/lib/server
+                ac_java_jvm_jni_lib_runtime_path="${ac_java_jvm_jni_lib_runtime_path}:$D"
+                ac_java_jvm_jni_lib_flags="$ac_java_jvm_jni_lib_flags -L$D -ljvm"
+            fi
+        fi


不過還沒辦法拿去 openSUSE build service 進行驗證。

BuildRequires:    java-1_8_0-openjdk-devel

目前主流的 JDK 版本仍然是 8,openSUSE build service 我暫時沒有更動。如果使用者升級到 OpenJDK 9,那麼要改 BuildRequires,
BuildRequires:    java-9-openjdk-devel

更新(目前的 Oracle 規畫):

所以如果是甲骨文,JDK 9 將會是一個過渡的版本,接下來將會推出 18.3,然後再來是 18.9,而 18.9 才是一個長期支援的版本。而 OpenJDK 對應的是 JDK Project,目前還沒有開 18.3 repositories 出來。

這表示大規模遷移到 Java 9 的機率不大,目前大多數仍然會留在 Java 8,直到下一個長期支援版本到來(目前 Oracle 設定為 18.9)。

這也代表我不用急著更新 RPM spec 以後丟上去 openSUSE build service 測試,至少在 18.9 出現以前,JDK 8 都會是目前的主要版本。

所以策略還是一樣,要等 openSUE LEAP 15 才知道是否需要修改 tclBlend RPM spec。如果預設的 JDK 還是 8,那就不用更動,如果升到 18.9,我就需要研究一下按照版本不同而進行設定的 BuildRequires。


openSUSE tcl-dbif: let it run

目前 openSUSE OBS 上找到的是 1.0 版,相依性的設定有設 tcl-dbus,所以要先安裝 tcl-dbus,然後才安裝 tcl-dbif。

sudo zypper install tcl-dbif

你很快就會發現無法正確使用。使用 package require dbif 並不會正確的找到套件。原因也很簡單,

在 tclsh 下使用下列的指令查詢:
::tcl::tm::path list

dbif 會安裝在 /usr/share/tcl/dbif1.0 目錄,但是這個目錄卻沒有在 openSUSE LEAP 42.3 tclsh 搜尋目錄的列表內。修正的方法很簡單,使用 ::tcl::tm::path 指令修正,在家目錄下的 ~/.tclshrc 一開始的時候加上我們想搜尋的目錄,
::tcl::tm::path add /usr/share/tcl/dbif1.0

這樣就可以讓 tcl-dbif 目錄正常的使用。但是會需要這樣是因為 .tclshrc 需要放在家目錄,這樣就…… 不知道怎麼從 rpm spec 修改。

* 原本我以為會去搜尋 /usr/share/tcl 目錄,結果沒有,但是還是可以透過修改 .tclshrc 方式來使用。

* 其實還有個違反 tm 檔案設計想法的改法,就是加上 pkgIndex.tcl,就可以讓 Tclsh 搜尋到。這個方法還沒測試。

更新:雖然和 tm 檔案設計的想法衝突,但是 RPM spec 加上 pkgIndex.tcl 就可以正常運作
使用 build script 建立 RPM 來測試 (注意:tcl-dbif 1.2 需要使用 tcl-dbus 2.1 版本才行)

The ActiveState of Tcl: TEApot and TEAcup Are Now Open Source

The ActiveState of Tcl: TEApot and TEAcup Are Now Open Source

所以 ActiveState 開源了  TEApot 和 TEAcup。Source code 放在 Gihub:

使用 Magicsplat Tcl/Tk for Windows 執行時出現問題。

tcllib 1.18 下載以後解壓縮,modules\fumagic,pkgIndex.tcl
if {![package vsatisfies [package provide Tcl] 8.4]} {return}

# Recognizers
package ifneeded fileutil::magic::filetype 1.0.2 [list source [file join $dir filetypes.tcl]]
package ifneeded fileutil::magic::mimetype 1.0.2 [list source [file join $dir mimetypes.tcl]]

# Runtime
package ifneeded fileutil::magic::rt 1.0 [list source [file join $dir rtcore.tcl]]

# Compiler packages
package ifneeded fileutil::magic::cgen   1.0 [list source [file join $dir cgen.tcl]]
package ifneeded fileutil::magic::cfront 1.0 [list source [file join $dir cfront.tcl]]

與 Magicsplat Tcl/Tk for Windows 的版本:
if {![package vsatisfies [package provide Tcl] 8.6]} {return}
# Recognizers
package ifneeded fileutil::magic::filetype 1.2.0 [list source [file join $dir filetypes.tcl]]

# Runtime
package ifneeded fileutil::magic::rt 1.2.0 [list source [file join $dir rtcore.tcl]]

# Compiler packages
package ifneeded fileutil::magic::cgen   1.2.0 [list source [file join $dir cgen.tcl]]
package ifneeded fileutil::magic::cfront 1.2.0 [list source [file join $dir cfront.tcl]]

在比對完 github 的 check-in 記錄以後,應該是有人直接修改了 tcllib 的部份,所以如果是從 github 或者是 1.18 release 之後(精確的說是 2016 年 6 月 13 日)從 trunk 拿的版本,就可能會出現這個問題。

* 即使 fileutil::magic::mimetype 問題解決,還是需要 Trf 套件
* 有 Trf 套件還是無法執行,需要修改 Trf 套件的 pkgIndex.tcl 版本號(ActiveTcl 8.6.6 issue)
* 改了版號還是無法執行,pref-devkit.tm 需要 projectInfo 套件
* 移除掉 package require projectInfo 也無法正確執行,沒有 jobs-async.tm 檔案

看起來我的做法錯誤, 下載 ActiveTcl 8.6.6 以後重做一次。

can't find package fileutil::magic::mimetype
    while executing
"package require fileutil::magic::mimetype   "
    (file "C:/Temp/teapot/lib/metadata/teapot-metadata-read.tm" line 38)

ActiveTcl 使用 Tcllib 1.18,所以不會有這個問題,如果有遇到;
更新 mimetype 變成 filetype (不知道這個做法會不會影響程式執行)

attempt to provide package Trf 2.1 failed: package Trf 2.1.4 provided instead
    ("package ifneeded Trf 2.1" script)

ActiveTcl 會遇到這個問題,把版本修正為精確版本。
更新 C:/ActiveTcl/lib/trf21/pkgIndex.tcl, 修改 2.1 為 2.1.4.

couldn't read file "C:/Temp/teapot/lib/misc/jobs-async.tm": no such file or dire
    while executing
"source C:/Temp/teapot/lib/misc/jobs-async.tm"
    ("package ifneeded jobs::async 0.1" script)
    invoked from within
"package require jobs::async"
    (file "C:/Temp/teapot/lib/tap/tap-db-files.tm" line 31)

更新 C:/Temp/teapot/lib/tap/tap-db-files.tm,
修改 jobs::async 變成 jobs

can't find package projectInfo
    while executing
"package require projectInfo     "
    (file "C:/Temp/teapot/lib/prefdk/pref-devkit.tm" line 44)

重做以後遇到這個問題,目前的暫時解法是移除掉package require projectInfo 這一行。


不過可惜的是到這裡還沒有結束,使用 tclsh main.tcl list [package name] 以後,會出現

Bad configuration
No archives known

的訊息,有可能是需要一些其它設定,那麼即使修改到可以執行,還是沒用(PS. 已經使用有包含 mimetype 的 1.18 版測試過,看起來不是這個套件的問題)。所以有可能需要包裝為 teapot 的工具並且經過適當設定才能夠使用。

外部的開發者無法在自己環境使用的工具等於沒有開源一樣,而等於沒有開源的意思就是還是只有 ActiveState 自己的人在開發。經過測試,雖然 ActiveState 開源了 teapot,但是就目前的有限資訊下,對於非 ActiveState 的人(甚至是 ActiveTcl 8.6.6 的使用者)來說是個沒有用的工具。

我發了一個訊息到 comp.lang.tcl,這樣如果有使用 fileutil::magic::mimetype 的人在 Tcllib 被修改以後的版本遇到問題,他們才知道發生了什麼情況。


Sugar: Lisp-like macro system for Tcl


會注意到是因為 Debian/Ubuntu 有這個套件 (套件名叫 tcl-sugar),然後就去 Tcler's wiki 看是不是有資料。沒想到可以用 pure Tcl 實作了一個類似 Lisp 的 macro system。


Magicsplat Tcl/Tk for Windows, Visual Studio 2017 and Jsonnet

Google Jsonnet 在 v0.9.5 加入一個 Visual Studio 2017 的專案檔,這表示已經成功的移植到 Windows(使用 Visual Studio 2017 編譯)。

我按照以前寫的 extension,改寫之前 win 目錄下的 makefile.vc 等檔案,試看看能不能使用 Visual Studio 2017 來編譯 tcljsonnet。結果出現一個很奇怪的狀況,在最後連結的時候,Visual Studio 2017 會一直抱怨 BAWT-Tcl (使用 MinGW-W64 編譯) 所使用的是舊的格式,我需要重新編譯才能夠正確連結。

經過思考,換成 Magicsplat Tcl/Tk for Windows 來測試(注:我安裝 在 c:\Tcl,而 Magicsplat  使用 Visual Studio 編譯),然後就很順利的編譯了,而且 tcljsonnet 也可以很正確的使用,不會有 C++ exception 模式不相容的問題。

但是這樣也代表,微軟 Windows 平台出現了格式的相容性問題,但是除非是一個環境中會使用 MinGW-W64 與 Visual Studio 2017 的人才會很快就遇到這個問題。

但是如果將 MinGW-W64 與 Visual Studio 2017 因為 binary 格式不相容而視為二個平台,就會出現很大的維護問題,至少對我而言會很吃力。而目前看起來,並沒有一個好的解法,還蠻麻煩的。 更新:我把使用 MinGW-64 編譯的 extension(例如我自己寫的 tcl-lmdb)放到 Magicsplat Tcl/Tk for Windows 的 lib 目錄下,發現可以使用,所以看起來問題沒那麼大。目前我還是以使用 Mingw-W64 為主,只有需要 Visual Studio 的時候才使用 Visual Studio。


BAWT-Tcl Windows installer

BAWT 提供了 8.6.x 系列的 Windows 安裝程式,並且安裝了一些常用的擴充套件以及 BAWT 作者自己的作品。

如果你使用 MSYS2/MinGW-w64 的組合,然後 Tcl 使用 BAWT-Tcl 安裝 (假設是安裝在 c:/Tcl),在編譯自己的套件時有可能會出現一些小亂流。

這是因為 tclConfig.shtkConfig.sh 在編譯的機器所指向的目錄和目標機器並不相同。也就是 TCL_PREFIX, TCL_EXEC_PREFIX, TCL_BUILD_LIB_SPEC, TCL_LIB_SPEC 等目錄所記錄的資料不是正確的。但是要修改很簡單,只要指向正確的目銾就可以解決問題,下面是一個例子:

# Top-level directory in which Tcl's platform-independent files are
# installed.



regex_rez: Tcl bindings for RE2


可是,我目前只需要基本功能,所以只實作最簡單的 fullmatch, partialmatch, replace 和 globalrelpace。

RE2 (software)

puts -nonewline "Please input a number: "
flush stdout
gets stdin number
if {$number <= 0 || $number >= 10} {
   puts "The range is 1 - 9."

set max [expr pow(10, $number)]
set result [list]

puts "\nNow get the result:"
puts "==========="
puts "Start"
puts "==========="
puts [time {
set re {1.*1|2.*2|3.*3|4.*4|5.*5|6.*6|7.*7|8.*8|9.*9|0.*0}
for {set i 1} {$i < $max} {incr i} {
    if {[regexp $re $i] != 1} {
        lappend result $i
} 1]
puts "\n"
puts [join $result ", "]
puts "\n==========="
puts "End"
puts "==========="

第二個測試程式使用 RE2:

package require regex_rez

puts -nonewline "Please input a number: "
flush stdout
gets stdin number
if {$number <= 0 || $number >= 10} {
   puts "The range is 1 - 9."

set max [expr pow(10, $number)]
set result [list]

puts "\nNow get the result:"
puts "==========="
puts "Start"
puts "==========="
puts [time {
set re [regex::re2 create {1.*1|2.*2|3.*3|4.*4|5.*5|6.*6|7.*7|8.*8|9.*9|0.*0}]
for {set i 1} {$i < $max} {incr i} {
    if {[$re partialmatch $i] != 1} {
        lappend result $i
} 1]
puts "\n"
puts [join $result ", "]
$re close
puts "\n==========="
puts "End"
puts "==========="

(* 更新測試程式二,加上 $re close,這樣才會正確 close)

475 microseconds per iteration
1029 microseconds per iteration
6884 microseconds per iteration
36152 microseconds per iteration
315566 microseconds per iteration
3134428 microseconds per iteration
32075931 microseconds per iteration
325211857 microseconds per iteration
2147483648 microseconds per iteration

RE2 從輸入數字一到數字九:
325 microseconds per iteration
649 microseconds per iteration
7262 microseconds per iteration
48204 microseconds per iteration
347111 microseconds per iteration
2727082 microseconds per iteration
21171756 microseconds per iteration
173788033 microseconds per iteration
1628503061 microseconds per iteration

雖然沒有很仔細的比較,在這個測試中如果是百萬級數目或者是以前比對,內建的正規表示式表現都很不錯,但是如果數目到達百萬級或者是以後比對,Tcl 內建的正規表示式速度看起來明顯的比較慢。



tclmixer - use SDL2/SDL2_mixer


TclMixer 使用 SDL 1.2 和 SDL_Mixer 1.2,我這幾天突然有個神奇的想法,那就是如果切換到 SDL2/SDL2_mixer 不知道會不會很難。

所以我下載了 TEA sample extension 然後將 TclMixer 的 source code 移過來,做了一點點小修改(主要是改版本,改成 2.0.0),然後發現…… SDL 和 SDL2 的 API 差距沒想像中大,因為沒改什麼東西,可是看起來 TclMixer 可以用。

實測 openSUSE 42.3 的結果,雖然可以使用,但是 SDL_mixer 還沒有 mp3 的部份,不過 OGG/Vorbis 是可用的。

MP3 的最後一個軟體專利在今年到期,所以一些原本無法直接使用的 MP3 自由軟體應該會開始逐漸重新進入 Linux 世界。至少在我測試 mpg123 的時候,我發現 openSUSE 在 42.3 將這個軟體放進來了(只是 devel package name 有改變,與 packman 的不同,但是主套件和 shared library package name 看起來是一致的)。


Tcl/Tk 8.7a1 RELEASED

Tcl/Tk 8.7a1 RELEASED

Tcl/Tk 8.7 alpha 1 釋出,這是第一個測試的 alpha 版本,但是離可以穩定使用應該還有一段距離才對。

This release is a development release, and should only be considered for deployment use after considerable testing.

    * [TIP 458] New notifiers based on epoll() or kqueue()
    * [TIP 166] Extended color notation for alpha channel with [$image get|put]
    * [TIP 442] New options to display text in a progressbar
    * [TIP 463] New option: [regsub -command]
    * [TIP 449] [text] undo/redo return character range
    * [TIP 472] Support "0d" as optional prefix for decimal integers
    * [TIP 459] New introspection: [package files]
    * [TIP 470] Access to the context objects in oo definition commands
         => TclOO 1.2.0
    * [TIP 456] Extended C interface for opening server socket
    * Socket accept callbacks now always eval in the global namespace
    * Undocumented command [tk_getFileType] removed
    * [array names -regexp] now supports backrefs
    * Support for and
    * New Win shell builtins: assoc ftype move
    * Fix crash when command overwrite triggers delete trace that
      deletes enclosing namespace.
    * Fix memory leak in [text] widget operations.
    * [spinbox] now handles reversed -to / -from ordering.
    * Revised port selection for server sockets.
    * Repaired shadowing problem with compatibility fonts.
    * Repaired threaded allocator initialization.
    * http 2.8.12 fixes state 100 continue handling
    * library/msgs/*.msg files converted to true utf-8.
    * Itcl 3 "wrong num args" error messages no longer repaired.


tDOM 0.9.0


tDOM 在很長一段時間的 0.8.3 以後,釋出了一個新版 v0.9.0。這版加入了 JSON 的支援,並且在編譯時開啟 html5 選項(需要連結 Google Gumbo html parser library)的情況下就有 html5 parser 可以用。

tDOM contains:

    *  for convenience expat 2.2.0, the XML parser originated from
       James Clark, although you're able to link tDOM with other
       expat versions or the library provided by the system.
    *  building a DOM tree from XML in one go implemented in C for
       maximum performance and minimum memory usage, and DOM I and II
       methods to work on such a tree using either a OO-like or a
       handle syntax.
    *  a Tcl interface to expat for event-like (SAX-like) XML parsing.
    *  a complete, compliant and fast XPath implementation in C
       following the November 99 W3C recommendation for navigating and
       data extraction.
    *  a fast XSLT implementation in C following the W3C Recommendation
       16 November 1999.
    *  optional DTD validation.
    *  a JSON parser which parses any possible JSON input into a DOM
       tree without losing information.
    *  an efficient and Tcl'ish way to create XML and HTML documents
       and JSON string.
    *  as build option an interface to the gumbo HTML5 parser, which
       also digests almost any other HTML.
    *  an even faster simple XML parser for trusted XML input.
    *  additional convenience methods.


tcl-rocksdb v0.1.1


RocksDB 更改了授權,從原本的 BSD+PATENTS 變成 BSD+PATENTS/GPL v2 (dual license) 然後又變成 Apache 2.0/GPL v2 (dual license)。

而會修改授權,是因為 Apache 基金會接到 Apache Cassandra Team 的請求,希望能夠釐清使用 RocksDB 是否會有版權上的疑慮以後,公佈了結果,Facebook BSD+PATENTS license 被列為 category-x,同時提出了下列的建議:
  • No new project, sub-project or codebase, which has not used Facebook BSD+patents licensed jars (or similar), are allowed to use them. In other words, if you haven't been using them, you aren't allowed to start. It is Cat-X.
  • If you have been using it, and have done so in a *release*, you have a temporary exclusion from the Cat-X classification thru August 31, 2017. At that point in time, ANY and ALL usage of these Facebook BSD+patents licensed artifacts are DISALLOWED. You must either find a suitably licensed replacement, or do without. There will be NO exceptions.
  • Any situation not covered by the above is an implicit DISALLOWAL of usage.

所以如果 RocksDB 沒有修改授權,那麼 Apache Cassandra 就無法使用 RocksDB,已經使用的 Apache Flink, Apache Samza 等各個專案也需要開始尋找替代品。而 RocksDB team 在 Apache 基金會公佈結果以後,很快就修改了授權,變成 Apache 2.0/GPL v2 (dual license)。

既然授權的疑慮清除(PATENTS 是很複雜的議題,如果可以,我會儘量避開),所以我實作了 tcl-rocksdb (prototype),實作一些最基本的操作,同時也寫了簡易的 test case 測試。因為其它的部份我不知道怎麼實作成 Tcl extension 比較好,所以目前的 v0.1.1 就是我實作上比較知道怎麼做的部份了。

加入幾個新的 command,更新版本為 v0.2。

另外,我發現 LevelDB 和 RocksDB 的基本操作長的很像,所以按照目前的資料,也寫了一個 tcl-leveldb


Tcl/Tk 8.6.7 RELEASED

Tcl / Tk 8.6.7 RELEASED

Tcl/Tk 釋出了新的版本 8.6.7,下面是修正的項目:

Summary of Changes since Tcl/Tk 8.6.6:

This is a patch release, so it primarily includes bug fixes and corrections to erratic behavior.  Highlighted changes are noted below. The changes file at the root of the source tree contains a more complete list.  The Timelines of all changes are online.


    * [TIP 473] (TclOO 1.1.0) Let [oo::copy] specify target namespace
    * [TIP 464] Support multimedia keys on Windows
    * Revise Tcl_LinkVar to tolerate some prefixes

    * Allow empty command as [interp alias] target

    * Revise Tcl_UtfToUniChar() handling of invalid UTF-8

    * [clock] and [encoding] are now ensembles
    * Several [clock] subcommands are now compiled to bytecode

    * Fix crashes or hangs in...
      - [socket -async ::1 0] with no host
      - Threaded memory allocator in thread finalization
      - Tcl_ListObjReplace() error when passed NULL interp
      - [binary scan a$n] when $n overflows int
      - [lsort -unique [lrepeat [expr {1<<27 1="" br="">      - Drawing many dashed canvas objects
      - Using Tk after IME restart
      - Warp pointer operations
      - [[text .t] insert 0.0 \na; .t replace 2.0 3.0 b]
      - Resource exhaustion processing corrupt GIF

    * Fix memory leaks in...
      - namespace delete when unset trace revives namespace variable.
      - Deletion of the [history] command
      - http::geturl when keep-alive is denied
      - TclJoinPath on a custom Tcl_Filesystem
      - text BTree operations

    * Update Unicode data to 10.0

    * Merge updates from new libtommath release; purge unused files

    * Update bundled zlib to release 1.2.11

    * Remove legacy support macro panic() - conflicts with system libs

    * Revise Tk font support to avoid type mismatch with recent Xft
    * Repair cmd resolver caching, demonstrated by tests resolver-3.1*
    * Repair zlib stream buffer flushing; zlib-7.8 and PNG writing
    * Prevent Win thread termination during init and teardown
    * Make KeyRelease event handling get _L and _R right
    * Repaint ttk::button when its image changes
    * Wrapped text don't start lines with whitespace
    * Non-native themes properly display tri-state buttons
    * File dialog repairs for -typevariable, -initialdir, -initialfile
    * Avoid unreleasable global grab due to menu keyboard traversal
    * Fix Win regression [event generate .e ]
    * Fix [grid configure -in] to fully clear outdated info
    * Fix ttk::combobox proper style with -postoffset (test combobox-3)
    * OSX: Stop [$text bbox] returning negative width
    * Repair autoloader fragility loading procs that call tailcall
    * Correct parsing in [scan 0x1 %b], [scan 0x1 %o]
    * Fix [string replace] so test stringComp-14.5 passes
    * Invalidate VFS mounts on sytem encoding change
    * Fix [expr {NaN > "Gran"}] to return 1; string comparison
    * Stop frequent Tcl_GetTime() calls creating clock drift
    * Correct functioning of dynamic unexport of methods
    * Fix [file join a //b] and [file join //a b]
    * Compute correct warp cursor position on 2nd display
    * Enable dismissal of ttk::menubutton without hover
    * Tk_BindEvent filter events Tk ignores avoid X11 ring buffer flood
    * Allow [$photo read] to read files beginning with "-"
    * [text] redisplay calculations corrected on OSX
    * Fix drawing of long text lines on Windows
    * Disabled combobox arrow appearance (danckaert)
    * Fix {PNG -alpha} format for 16-bit color
    * Workaround X11 drawing defects in Ubuntu 16.10+
    * Fix calculation of ttk::notebook tab widths and tab user management
    * Scidb race in notebook tab selection
    * Color name parsing set to reject invalid hex color codes (#ABCZ)
    * OSX scrollbar draw position, highlights, & smooth scroll
    * Race condition on Win clipboard cleanup
    * Center image on button
    * Paneconfigure get pane heights right
    * Windows: User switch forced theme reset
    * Updated bundled packages
       - Itcl 4.1.0      (incompatible with Itk 4.0, get Itk 4.1)
       - sqlite3 3.20.0
       - Thread 2.8.1
       - tdbc* 1.0.5
       - http 2.8.11
       - tcltest 2.4.1
       - msgcat 1.6.1


Apache Tomcat HTTP/2 與 TclCurl

一閈始是設定 Apache Tomcat HTTP/2 的部份(Apache Tomcat 在 8.5 及以後的版本開始支援)。

HTTP/2 需要 Apache Tomcat Native Library 的支援才行, 所以首先是先安裝 Apache Tomcat Native Library。

Tomcat Native Library 需要 libapr1,所以如果是使用 openSUSE,
sudo zypper install libapr1-devel

再來是自己編譯 Tomcat 或者是使用 RPM 安裝

然後,需要創造 OpenSSL Certificate。 可以參考 Howto: Make Your Own Cert With OpenSSL。

如果只是要自己測試使用所以使用 self-signed,在 conf 目錄下使用下列的指令:
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 1826 -key ca.key -out ca.crt

最後修改 conf 目錄下的 server.xml,開啟 HTTP/2 的支援並且修改如下:

    <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
               maxThreads="150" SSLEnabled="true" >
        <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
            <Certificate certificateKeyFile="conf/ca.key"
                         certificateFile="conf/ca.crt" />

再來重新開啟 Apache Tomcat server,如果 https://localhost:8443 可以瀏覽,就正確的設定好了(* 因為是 self-signed,所以會看到安全性警告)。

TclCurl 在最近的版本有支援 HTTP/2,下面是驗證 Apache Tomcat HTTP/2 (self-signed) 的範例:
package require TclCurl

set curlHandle [curl::init]
$curlHandle configure -url https://localhost:8443 -sslverifyhost 0 \
    -sslverifypeer 0 -bodyvar result
$curlHandle setopt CURLOPT_HTTP_VERSION 2TLS

catch { $curlHandle perform } curlErrorNumber
if { $curlErrorNumber != 0 } {
    puts "error [curl::easystrerror $curlErrorNumber]"

# Print result and clean up
puts $result
$curlHandle cleanup

那麼我們怎麼知道真的是使用 HTTP/2 協定呢?這只要檢查 Tomcat logs 目錄下的 log 就可以確定。

Tcl/Tk 8.6.7 Release Candidates

Tcl/Tk 8.6.7 Release Candidates

8.6.7 RC1 source package.

This collection includes release candidates of packages:
Itcl 4.1.0
http 2.8.11
Thread 2.8.1
msgcat 1.6.1
sqlite3 3.20.0
tcltest 2.4.1
tdbc* 1.0.5