summaryrefslogtreecommitdiffstats
path: root/library
diff options
context:
space:
mode:
Diffstat (limited to 'library')
-rw-r--r--library/http/http.tcl148
1 files changed, 103 insertions, 45 deletions
diff --git a/library/http/http.tcl b/library/http/http.tcl
index c159cb5..f7dae16 100644
--- a/library/http/http.tcl
+++ b/library/http/http.tcl
@@ -119,7 +119,7 @@ namespace eval http {
variable urlTypes
if {![info exists urlTypes]} {
- set urlTypes(http) [list 80 ::http::socket]
+ set urlTypes(http) [list 80 ::http::AltSocket {} 1 0]
}
variable encodings [string tolower [encoding names]]
@@ -282,15 +282,34 @@ if {[info command http::Log] eq {}} {proc http::Log {args} {}}
# See documentation for details.
#
# Arguments:
-# proto URL protocol prefix, e.g. https
-# port Default port for protocol
-# command Command to use to create socket
+# proto URL protocol prefix, e.g. https
+# port Default port for protocol
+# command Command to use to create socket
+# socketCmdVarName (optional) name of variable provided by the protocol
+# handler whose value is the callback used by argument
+# "command" to open a socket. The default value "::socket"
+# will be overwritten by http.
+# useSockThread (optional, boolean)
+# endToEndProxy (optional, boolean)
# Results:
-# list of port and command that was registered.
+# list of port, command, variable name, (boolean) threadability,
+# and (boolean) endToEndProxy that was registered.
-proc http::register {proto port command} {
+proc http::register {proto port command {socketCmdVarName {}} {useSockThread 0} {endToEndProxy 0}} {
variable urlTypes
- set urlTypes([string tolower $proto]) [list $port $command]
+ set lower [string tolower $proto]
+ if {[info exists urlTypes($lower)]} {
+ unregister $lower
+ }
+ set urlTypes($lower) [list $port $command $socketCmdVarName $useSockThread $endToEndProxy]
+
+ # If the external handler for protocol $proto has given $socketCmdVarName the expected
+ # value "::socket", overwrite it with the new value.
+ if {($socketCmdVarName ne {}) && ([set $socketCmdVarName] eq {::socket})} {
+ set $socketCmdVarName ::http::socketAsCallback
+ }
+
+ return $urlTypes($lower)
}
# http::unregister --
@@ -300,7 +319,8 @@ proc http::register {proto port command} {
# Arguments:
# proto URL protocol prefix, e.g. https
# Results:
-# list of port and command that was unregistered.
+# list of port, command, variable name, (boolean) useSockThread,
+# and (boolean) endToEndProxy that was unregistered.
proc http::unregister {proto} {
variable urlTypes
@@ -309,6 +329,13 @@ proc http::unregister {proto} {
return -code error "unsupported url type \"$proto\""
}
set old $urlTypes($lower)
+
+ # Restore the external handler's original value for $socketCmdVarName.
+ lassign $old defport defcmd socketCmdVarName useSockThread endToEndProxy
+ if {($socketCmdVarName ne {}) && ([set $socketCmdVarName] eq {::http::socketAsCallback})} {
+ set $socketCmdVarName ::socket
+ }
+
unset urlTypes($lower)
return $old
}
@@ -928,22 +955,19 @@ proc http::geturl {url args} {
variable urlTypes
# - If ::tls::socketCmd has its default value "::socket", change it to the
- # new value ::http::socketForTls.
+ # new value ::http::socketAsCallback.
# - If the old value is different, then it has been modified either by the
# script or by the Tcl installation, and replaced by a new command. The
# script or installation that modified ::tls::socketCmd is also
- # responsible for integrating ::http::socketForTls into its own "new"
+ # responsible for integrating ::http::socketAsCallback into its own "new"
# command, if it wishes to do so.
# - Commands that open a socket:
- # - ::socket - basic
- # - ::http::socket - can use a thread to avoid blockage by slow DNS
- # lookup. See http::config option -threadlevel.
- # - ::http::socketForTls - as ::http::socket, but can also open a socket
- # for HTTPS/TLS through a proxy.
-
- if {[info exists ::tls::socketCmd] && ($::tls::socketCmd eq {::socket})} {
- set ::tls::socketCmd ::http::socketForTls
- }
+ # - ::socket - basic
+ # - ::http::AltSocket - can use a thread to avoid blockage by slow
+ # DNS lookup. See http::config option
+ # -threadlevel.
+ # - ::http::socketAsCallback - as ::http::AltSocket, but can also open a
+ # socket for HTTPS/TLS through a proxy.
set token [CreateToken $url {*}$args]
variable $token
@@ -1067,6 +1091,8 @@ proc http::CreateToken {url args} {
requestLine {}
transfer {}
proxyUsed none
+ protoSockThread 0
+ protoProxyConn 0
}
set state(-keepalive) $defaultKeepalive
set state(-strict) $strict
@@ -1261,8 +1287,16 @@ proc http::CreateToken {url args} {
unset $token
return -code error "Unsupported URL type \"$proto\""
}
- set defport [lindex $urlTypes($lower) 0]
- set defcmd [lindex $urlTypes($lower) 1]
+ lassign $urlTypes($lower) defport defcmd socketCmdVarName useSockThread end2EndProxy
+
+ # If the external handler for protocol $proto has given $socketCmdVarName the expected
+ # value "::socket", overwrite it with the new value.
+ if {($socketCmdVarName ne {}) && ([set $socketCmdVarName] eq {::socket})} {
+ set $socketCmdVarName ::http::socketAsCallback
+ }
+
+ set state(protoSockThread) $useSockThread
+ set state(protoProxyConn) $end2EndProxy
if {$port eq ""} {
set port $defport
@@ -1344,12 +1378,12 @@ proc http::CreateToken {url args} {
}
# Handle proxy requests here for http:// but not for https://
- # The proxying for https is done in the ::http::socketForTls command.
+ # The proxying for https is done in the ::http::socketAsCallback command.
# A proxy request for http:// needs the full URL in the HTTP request line,
# including the server name.
# The *tls* test below attempts to describe protocols in addition to
# "https on port 443" that use HTTP over TLS.
- if {($phost ne "") && (![string match -nocase *tls* $defcmd])} {
+ if {($phost ne "") && (!$end2EndProxy)} {
set srvurl $url
set targetAddr [list $phost $pport]
set state(proxyUsed) HttpProxy
@@ -1369,8 +1403,13 @@ proc http::CreateToken {url args} {
lappend sockopts -myaddr $state(-myaddr)
}
+ if {$useSockThread} {
+ set targs [list -type $token]
+ } else {
+ set targs {}
+ }
set state(connArgs) [list $proto $phost $srvurl]
- set state(openCmd) [list {*}$defcmd {*}$sockopts -type $token {*}$targetAddr]
+ set state(openCmd) [list {*}$defcmd {*}$sockopts {*}$targs {*}$targetAddr]
# See if we are supposed to use a previously opened channel.
# - In principle, ANY call to http::geturl could use a previously opened
@@ -4946,11 +4985,11 @@ interp alias {} http::ncode {} http::responseCode
# ------------------------------------------------------------------------------
-# Proc http::socketForTls
+# Proc http::socketAsCallback
# ------------------------------------------------------------------------------
# Command to use in place of ::socket as the value of ::tls::socketCmd.
-# This command does the same as http::socket, and also handles https connections
-# through a proxy server.
+# This command does the same as http::AltSocket, and also handles https
+# connections through a proxy server.
#
# Notes.
# - The proxy server works differently for https and http. This implementation
@@ -4967,12 +5006,23 @@ interp alias {} http::ncode {} http::responseCode
# Return Value: a socket identifier
# ------------------------------------------------------------------------------
-proc http::socketForTls {args} {
+proc http::socketAsCallback {args} {
variable http
+
+ set targ [lsearch -exact $args -type]
+ if {$targ != -1} {
+ set token [lindex $args $targ+1]
+ upvar 0 ${token} state
+ set protoProxyConn $state(protoProxyConn)
+ } else {
+ set protoProxyConn 0
+ }
+
set host [lindex $args end-1]
set port [lindex $args end]
if { ($http(-proxyfilter) ne {})
&& (![catch {$http(-proxyfilter) $host} proxy])
+ && $protoProxyConn
} {
set phost [lindex $proxy 0]
set pport [lindex $proxy 1]
@@ -4981,7 +5031,7 @@ proc http::socketForTls {args} {
set pport {}
}
if {$phost eq ""} {
- set sock [::http::socket {*}$args]
+ set sock [::http::AltSocket {*}$args]
} else {
set sock [::http::SecureProxyConnect {*}$args $phost $pport]
}
@@ -5036,8 +5086,8 @@ proc http::SecureProxyConnect {args} {
# Elements of args other than host and port are not used when
# AsyncTransaction opens a socket. Those elements are -async and the
# -type $tokenName for the https transaction. Option -async is used by
- # AsyncTransaction anyway, and -type $tokenName should not be propagated:
- # the proxy request adds its own -type value.
+ # AsyncTransaction anyway, and -type $tokenName should not be
+ # propagated: the proxy request adds its own -type value.
set targ [lsearch -exact $args -type]
if {$targ != -1} {
@@ -5182,14 +5232,14 @@ proc http::AllDone {varName args} {
# ------------------------------------------------------------------------------
-# Proc http::socket
+# Proc http::AltSocket
# ------------------------------------------------------------------------------
# This command is a drop-in replacement for ::socket.
# Arguments and return value as for ::socket.
#
# Notes.
-# - http::socket is specified in place of ::socket by the definition of urlTypes
-# in the namespace header of this file (http.tcl).
+# - http::AltSocket is specified in place of ::socket by the definition of
+# urlTypes in the namespace header of this file (http.tcl).
# - The command makes a simple call to ::socket unless the user has called
# http::config to change the value of -threadlevel from the default value 0.
# - For -threadlevel 1 or 2, if the Thread package is available, the command
@@ -5200,18 +5250,20 @@ proc http::AllDone {varName args} {
# - FIXME The peer thread can transfer the socket only to the main interpreter
# in the present thread. Therefore this code works only if this script runs
# in the main interpreter. In a child interpreter, the parent must alias a
-# command to ::http::socket in the child, run http::socket in the parent,
-# and then transfer the socket to the child.
-# - The http::socket command is simple, and can easily be replaced with an
+# command to ::http::AltSocket in the child, run http::AltSocket in the
+# parent, and then transfer the socket to the child.
+# - The http::AltSocket command is simple, and can easily be replaced with an
# alternative command that uses a different technique to open a socket while
# entering the event loop.
# - Unexpected behaviour by thread::send -async (Thread 2.8.6).
# An error in thread::send -async causes return of just the error message
# (not the expected 3 elements), and raises a bgerror in the main thread.
# Hence wrap the command with catch as a precaution.
+# - Bug in Thread 2.8.8 - on Windows, read/write operations fail on a socket
+# moved from another thread by thread::transfer.
# ------------------------------------------------------------------------------
-proc http::socket {args} {
+proc http::AltSocket {args} {
variable ThreadVar
variable ThreadCounter
variable http
@@ -5225,7 +5277,8 @@ proc http::socket {args} {
upvar 0 $token state
}
- if {!$http(usingThread)} {
+ if {$http(usingThread) && [info exists state] && $state(protoSockThread)} {
+ } else {
# Use plain "::socket". This is the default.
return [eval ::socket $args]
}
@@ -5281,7 +5334,7 @@ proc http::socket {args} {
return -options $errdict -code $catchCode $sock
}
-# The commands below are dependencies of http::socket and
+# The commands below are dependencies of http::AltSocket and
# http::SecureProxyConnect and are not used elsewhere.
# ------------------------------------------------------------------------------
@@ -5293,21 +5346,26 @@ proc http::socket {args} {
# value 1 => operate as if -threadlevel 0
# value 2 => error return
#
+# The command assigns a value to http(usingThread), which records whether
+# command http::AltSocket can use a separate thread.
+#
# Arguments: none
# Return Value: none
# ------------------------------------------------------------------------------
proc http::LoadThreadIfNeeded {} {
variable http
- if {$http(usingThread) || ($http(-threadlevel) == 0)} {
+ if {$http(-threadlevel) == 0} {
+ set http(usingThread) 0
return
}
- if {[catch {package require Thread}]} {
+ if {[catch {package require Thread 2.8.9-}]} {
if {$http(-threadlevel) == 2} {
set msg {[http::config -threadlevel] has value 2,\
- but the Thread package is not available}
+ but the Thread package (2.8.9 or above) is not available}
return -code error $msg
}
+ set http(usingThread) 0
return
}
set http(usingThread) 1
@@ -5318,7 +5376,7 @@ proc http::LoadThreadIfNeeded {} {
# ------------------------------------------------------------------------------
# Proc http::SockInThread
# ------------------------------------------------------------------------------
-# Command http::socket is a ::socket replacement. It defines and runs this
+# Command http::AltSocket is a ::socket replacement. It defines and runs this
# command, http::SockInThread, in a peer thread.
#
# Arguments:
@@ -5331,7 +5389,7 @@ proc http::LoadThreadIfNeeded {} {
# ------------------------------------------------------------------------------
proc http::SockInThread {caller defcmd sockargs} {
- package require Thread
+ package require Thread 2.8.9-
set catchCode [catch {eval $defcmd $sockargs} sock errdict]
if {$catchCode == 0} {