bgdelivery proc returnfile (public)

 bgdelivery[i] returnfile [ -client_data client_data ] [ -delete delete ] \
    [ -content_disposition content_disposition ] status_code mime_type \
    filename

Defined in packages/xotcl-core/tcl/bgdelivery-procs.tcl

Deliver the given file to the requester in the background. This proc uses the background delivery thread to send the file in an event-driven manner without blocking a request thread. This is especially important when large files are requested over slow connections. With NaviServer, this function is mostly obsolete, at least, when writer threads are configured. The writer threads have as well the advantage, that these can be used with https, while the bgdelivery thread works directly on the socket. One remaining purpose of this function is h264 streaming delivery (when the module is in use).

Switches:
-client_data
(optional)
-delete
(defaults to "false") (optional)
-content_disposition
(optional)
Parameters:
status_code
mime_type
filename

Partial Call Graph (max 5 caller/called nodes):
%3 ad_conn ad_conn (public) ad_file ad_file (public) security::secure_conn_p security::secure_conn_p (public) thread::transfer thread::transfer xo::backslash_escape xo::backslash_escape bgdelivery proc returnfile bgdelivery proc returnfile bgdelivery proc returnfile->ad_conn bgdelivery proc returnfile->ad_file bgdelivery proc returnfile->security::secure_conn_p bgdelivery proc returnfile->thread::transfer bgdelivery proc returnfile->xo::backslash_escape

Testcases:
No testcase defined.
Source code:

#ns_setexpires 1000000
#ns_log notice "expires-set $filename"
#ns_log notice "status_code = $status_code, filename=$filename"

if {![nsf::is object ::xo::cc]} {
  ::xo::ConnectionContext require -url [ad_conn url]
}
set query [::xo::cc actual_query]
set secure_conn_p [security::secure_conn_p]
set use_h264 [expr {[string match "video/mp4*" $mime_type] && $query ne ""
                    && ([string match {*start=[1-9]*} $query] || [string match {*end=[1-9]*} $query])
                    && [info commands h264open] ne ""
                    && !$secure_conn_p }]

if {[info commands ns_driversection] ne ""} {
  set use_writerThread [ns_config [ns_driversection] writerthreads 0]
} else {
  set use_writerThread 0
}

if {[info exists content_disposition]} {
  set fn [xo::backslash_escape \" $content_disposition]
  ns_set put [ns_conn outputheaders] Content-Disposition "attachment;filename=\"$fn\""
}

if {$secure_conn_p && !$use_writerThread} {
  #
  # The bgdelivery thread does not work over https, so fall back
  # to ns_returnfile. The writer thread works fine with https.
  #
  ns_returnfile $status_code $mime_type $filename
  return
}

if {$use_h264} {
  if {0} {
    # we have to obtain the size from the file; unfortunately, this
    # requires a duplicate open+close of the h264 stream. If the
    # application is performance sensitive, one might consider to use
    # the possibly incorrect size form the filesystem instead (works
    # perfectly for e.g. flowplayer)
    if {[catch {set handle [h264open $filename $query]} errorMsg]} {
      ns_log error "h264: error opening h264 channel for $filename $query: $errorMsg"
      return
    }
    set size [h264length $handle]
    h264close $handle
  } else {
    set size [ad_file size $filename]
  }
} else {
  set size [ad_file size $filename]
}

# Make sure to set "connection close" for the requests (in other
# words, don't allow keep-alive, which is does not make sense, when
# we close the connections manually in the bgdelivery thread).
#
if {$::xo::naviserver && !$use_writerThread} {
  ns_conn keepalive 0
}

set range [ns_set iget [ns_conn headers] range]
if {[regexp {bytes=(.*)$} $range _ range]} {
  set ranges [list]
  set bytes 0
  set pos 0
  foreach r [split $range ,] {
    regexp {^(\d*)-(\d*)$} $r _ from to
    if {$from eq ""} {
      # The last $to bytes, $to must be specified; 'to' is
      # differently interpreted as in the case, where from is
      # nonempty
      set from [expr {$size - $to}]
    } else {
      if {$to eq ""} {set to [expr {$size-1}]}
    }
    set rangeSize [expr {1 + $to - $from}]
    lappend ranges [list $from $to $rangeSize]
    set pos [expr {$to + 1}]
    incr bytes $rangeSize
  }
} else {
  set ranges ""
  set bytes $size
}

#ns_log notice "Range=$range bytes=$bytes // $ranges"

#
# For the time being, we write the headers in a simplified version
# directly in the spooling thread to avoid the overhead of double
# h264opens.
#
if {!$use_h264} {
  #
  # Add content-range header for range requests.
  #
  if {[llength $ranges] == 1 && $status_code == 200} {
    lassign [lindex $ranges 0] from to
    if {$from <= $to && $size > $to} {
      ns_set put [ns_conn outputheaders] Content-Range "bytes $from-$to/$size"
      #ns_log notice "given range <$range>, added header-field Content-Range: bytes $from-$to/$size // $ranges"
      set status_code 206
    } else {
      # A byte-content-range-spec with a byte-range-resp-spec whose
      # last-byte-pos value is less than its first-byte-pos value,
      # or whose instance-length value is less than or equal to its
      # last-byte-pos value, is invalid. The recipient of an invalid
      # byte-content-range-spec MUST ignore it and any content
      # transferred along with it.
      #
      # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html (14.16)
      #
      ns_log notice "### ignore invalid range <$range>, pos > size-1, Content-Range: bytes $from-$to/$size // $ranges"
    }
  } elseif {[llength $ranges]>1} {
    ns_log warning "Multiple ranges are currently not supported, ignoring range request"
  }
  if {$::xo::naviserver && ![string match text/* $mime_type]} {
    :write_headers -binary -- $status_code $mime_type $bytes
  } else {
    :write_headers $status_code $mime_type $bytes
  }
}

if {$bytes == 0} {
  # Tcl behaves different, when one tries to send 0 bytes via
  # file_copy. So, we handle this special case here...
  # There is actually nothing to deliver....
  ns_set put [ns_conn outputheaders] "Content-Length" 0
  ns_return 200 $mime_type {}
  return
}

if {$use_writerThread && !$use_h264} {
  if {$status_code == 206} {
    ns_log notice "ns_writer submitfile -offset $from -size $bytes $filename"
    ns_writer submitfile -offset $from -size $bytes $filename
  } else {
    ns_log notice "ns_writer submitfile $filename"
    ns_writer submitfile $filename
  }
  return
}

set errorMsg ""
# Get the thread id and make sure the bgdelivery thread is already
# running.
set tid [:get_tid]

# :log "+++ lock ${:bgmutex}"
ns_mutex_lock ${:mutex}

#
# Transfer the channel to the bgdelivery thread and report errors
# in detail.
#
# Notice, that Tcl versions up to 8.5.4 have a bug in this area.
# If one uses an earlier version of Tcl, please apply:
# http://tcl.cvs.sourceforge.net/viewvc/tcl/tcl/generic/tclIO.c?r1=1.61.2.29&r2=1.61.2.30&pathrev=core-8-4-branch
#

catch {
  set ch [ns_conn channel]
  if {[catch {thread::transfer $tid $ch} innerError]} {
    set channels_in_use "??"
    catch {set channels_in_use [bgdelivery do file channels]}
    ns_log error "thread transfer failed, channel=$ch, channels_in_use=$channels_in_use"
    error $innerError
  }
} errorMsg

ns_mutex_unlock ${:mutex}
#ns_mutex unlock ${:bgmutex}
# :log "+++ unlock ${:bgmutex}"

if {$errorMsg ne ""} {
  error ERROR=$errorMsg
}

if {$use_h264} {
  #:log "MP4 q=[::xo::cc actual_query], h=[ns_set array [ns_conn outputheaders]]"
  :do -async ::h264Spooler spool -delete $delete -channel $ch -filename $filename  -context [list [::xo::cc requester],[::xo::cc url],$query [ns_conn start]]  -query $query  -client_data $client_data
} else {
  #:log "FILE SPOOL $filename"
  :do -async ::fileSpooler spool -ranges $ranges -delete $delete -channel $ch -filename $filename  -context [list [::xo::cc requester],[::xo::cc url],$query [ns_conn start]]  -client_data $client_data
}
#
# set the length for the access log (which is written when the
# connection thread is done)
ns_conn contentsentlength $size       ;# maybe overly optimistic
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: