Tcl Library Source Code

Check-in [0b3dd048f8]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Expands support for json encoded arguments. This lets rest.tcl be used with Gemini and openai REST interfaces.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | trunk
Files: files | file ages | folders
SHA3-256: 0b3dd048f836fbbc0832b6e731debcfe65eb3087f64884c69cd65197e5cd7883
User & Date: clif 2025-07-16 13:30:15.865
Context
2025-07-16
13:30
Expands support for json encoded arguments. This lets rest.tcl be used with Gemini and openai REST interfaces. Leaf check-in: 0b3dd048f8 user: clif tags: trunk
2024-11-15
08:12
Correct a typo in the description of the elliptic function dn (K.Koehler) check-in: 61e6db60d3 user: arjenmarkus tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to modules/rest/rest.tcl.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# rest.tcl --
#
# A framework for RESTful web services
#
# Copyright (c) 2009 Aaron Faupell

package require Tcl 8.5 9
package require http 2.7
package require json
package require tdom
package require base64

package provide rest 1.7

namespace eval ::rest {
    namespace export create_interface parameters parse_opts save \
    describe substitute
}

# simple --






|





|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# rest.tcl --
#
# A framework for RESTful web services
#
# Copyright (c) 2009 Aaron Faupell

package require Tcl 8.5 9+
package require http 2.7
package require json
package require tdom
package require base64

package provide rest 1.8

namespace eval ::rest {
    namespace export create_interface parameters parse_opts save \
    describe substitute
}

# simple --
39
40
41
42
43
44
45






46
47
48
49
50
51
52
53

54
55
56
57
58
59
60
        set body [lindex $opts 1]
    } else {
        set body [lindex $args 1]
    }

    DetermineMethod config







    if {[string first " " $query] > 0} {
        # if query has a space assume it is a list of key value pairs, and do the formatting
        set query [::http::formatQuery {*}$query]
    } elseif {[string first ? $url] > 0 && $query == ""} {
        # if the url contains a query string and query empty then split it to the correct vars
        set query [join [lrange [split $url ?] 1 end] ?]
        set url [lindex [split $url ?] 0]
    }


    if {[dict exists $config auth]} {
        set auth [dict get $config auth]
        if {[lindex $auth 0] == "basic"} {
            lappend headers Authorization "Basic [base64::encode [lindex $auth 1]:[lindex $auth 2]]"
	} elseif {[lindex $auth 0] == "bearer"} {
            lappend headers Authorization "Bearer [lindex $auth 1]"







>
>
>
>
>
>
|
<






>







39
40
41
42
43
44
45
46
47
48
49
50
51
52

53
54
55
56
57
58
59
60
61
62
63
64
65
66
        set body [lindex $opts 1]
    } else {
        set body [lindex $args 1]
    }

    DetermineMethod config

#### CLIF FIX
    # if query has a space assume it might be a list of key value pairs, 
    #   and do the formatting
    # Unless there's a space and it's a single list element,
    #   then it's more likely a json encoded string, so don't touch it.

  if {([string first " " $query] > 0) && ([llength $query] > 1)} {

        set query [::http::formatQuery {*}$query]
    } elseif {[string first ? $url] > 0 && $query == ""} {
        # if the url contains a query string and query empty then split it to the correct vars
        set query [join [lrange [split $url ?] 1 end] ?]
        set url [lindex [split $url ?] 0]
    }
#### CLIF FIX

    if {[dict exists $config auth]} {
        set auth [dict get $config auth]
        if {[lindex $auth 0] == "basic"} {
            lappend headers Authorization "Basic [base64::encode [lindex $auth 1]:[lindex $auth 2]]"
	} elseif {[lindex $auth 0] == "bearer"} {
            lappend headers Authorization "Bearer [lindex $auth 1]"
144
145
146
147
148
149
150

151
152
153
154
155
156
157
    #            break
    #        }
    #    }
    #}

    namespace eval ::$name {}
    foreach call [array names in] {

        set config $in($call)
        set proc [list]

        if {[dict exists $config copy]} {
            set config [dict merge $in([dict get $config copy]) $config]
        }
        if {[dict exists $config unset]} {







>







150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
    #            break
    #        }
    #    }
    #}

    namespace eval ::$name {}
    foreach call [array names in] {

        set config $in($call)
        set proc [list]

        if {[dict exists $config copy]} {
            set config [dict merge $in([dict get $config copy]) $config]
        }
        if {[dict exists $config unset]} {
228
229
230
231
232
233
234















235








236
237
238
239
240
241
242
            lappend proc {lappend headers Cookie [join [dict get $config cookie] \;]}
        }
        _transform $name $call $config proc input_transform query
        if {[dict exists $config auth] && [lindex [dict get $config auth] 0] == "sign"} {
            lappend proc "set query \[::${name}::[lindex [dict get $config auth] 1] \$query]"
        }
















        lappend proc {set query [::http::formatQuery {*}$query]}









        # if this is an async call (has defined a callback)
        # then end the main proc here by returning the http token
        # the rest of the normal result processing will be put in a _callback_NAME
        # proc which is called by the generic _callback proc
        if {[dict exists $config callback]} {
            lappend proc "set t \[::rest::_call \{[list ::${name}::_callback_$call [dict get $config callback]]\} \$headers \$url \$query \$body \$error_body]"







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>







235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
            lappend proc {lappend headers Cookie [join [dict get $config cookie] \;]}
        }
        _transform $name $call $config proc input_transform query
        if {[dict exists $config auth] && [lindex [dict get $config auth] 0] == "sign"} {
            lappend proc "set query \[::${name}::[lindex [dict get $config auth] 1] \$query]"
        }

#        lappend proc {set query [::http::formatQuery {*}$query]}
# CLIF FIX
        # If there's headers, and if there's content-type,
	# and if that's application/json, use json format
	# else use formatQuery
        if {[dict exists $config headers]} {
	  set headers [string tolower [dict get $config headers]]
	  if {[dict exists $headers content-type]} {
	    set ctype [dict get $headers content-type]
	    switch $ctype {
	      application/json {
                lappend proc {set query [json::write object-string {*}$query]}
	      }
	      application/x-www-form-urlencoded -
	      default {
                lappend proc {set query [::http::formatQuery {*}$query]}
	      }
	    }
	  }
	} else {
          lappend proc {set query [::http::formatQuery {*}$query]}
	}
	
# CLIF FIX

        # if this is an async call (has defined a callback)
        # then end the main proc here by returning the http token
        # the rest of the normal result processing will be put in a _callback_NAME
        # proc which is called by the generic _callback proc
        if {[dict exists $config callback]} {
            lappend proc "set t \[::rest::_call \{[list ::${name}::_callback_$call [dict get $config callback]]\} \$headers \$url \$query \$body \$error_body]"
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# EFFECTS:
#       creates a new namespace and builds api procedures within it
#
# RETURNS:
#       the data from the http reply, or an http token if the request was async
#
proc ::rest::_call {callback headers url query body error_body} {
    #puts "_call [list $callback $headers $url $query $body $error_body]"
    # get the settings from the calling proc
    upvar config config

    set method GET
    if {[dict exists $config method]} { set method [string toupper [dict get $config method]] }

    # assume the query should really be the body for post or put requests







|







464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# EFFECTS:
#       creates a new namespace and builds api procedures within it
#
# RETURNS:
#       the data from the http reply, or an http token if the request was async
#
proc ::rest::_call {callback headers url query body error_body} {
    puts "_call [list C: $callback\n H: $headers\n U $url\n Q $query\n B $body\n E $error_body]"
    # get the settings from the calling proc
    upvar config config

    set method GET
    if {[dict exists $config method]} { set method [string toupper [dict get $config method]] }

    # assume the query should really be the body for post or put requests
474
475
476
477
478
479
480



481




482
483
484
485
486
487
488
489
490
491
492
493
494



495




496
497
498
499
500
501
502
        lappend opts -timeout [dict get $config timeout]
    }

    #puts "headers $headers"
    #puts "opts $opts"
    #puts "geturl $url"
    #return



    set t        [http::geturl $url -headers $headers {*}$opts]




    set data     [http::data  $t]
    set httpCode [http::ncode $t]

    # if this is an async request return now, otherwise process the
    # result
    if {$callback != ""} { return $t }

    # Generate an error return for a failed request
    if {![string match 2* $httpCode]} {
        #parray $t
	set retList [list HTTP $httpCode]
        if {[string match {30[123]} $httpCode]} {
            upvar #0 $t a



            lappend retList [dict get $a(meta) Location]




        }
        if {$error_body} {lappend retList $data}
        return -code error $retList
    }

    # copy the token into the calling scope so that the transforms can
    # access it via uplevel, and we can still call cleanup on the real







>
>
>
|
>
>
>
>













>
>
>
|
>
>
>
>







504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
        lappend opts -timeout [dict get $config timeout]
    }

    #puts "headers $headers"
    #puts "opts $opts"
    #puts "geturl $url"
    #return

# CLIF FIX
    if {$headers ne ""} {
      set t        [http::geturl $url -headers $headers {*}$opts]
    } else {
      set t        [http::geturl $url {*}$opts]
    }
# CLIF FIX
    set data     [http::data  $t]
    set httpCode [http::ncode $t]

    # if this is an async request return now, otherwise process the
    # result
    if {$callback != ""} { return $t }

    # Generate an error return for a failed request
    if {![string match 2* $httpCode]} {
        #parray $t
	set retList [list HTTP $httpCode]
        if {[string match {30[123]} $httpCode]} {
            upvar #0 $t a

### CLIF FIX ###
	    if {[dict exists $a(meta) Location]} {
              lappend retList [dict get $a(meta) Location]
	    } else {
	      lappend retList "Moved to undefined location"
	    }
### CLIF FIX ###
        }
        if {$error_body} {lappend retList $data}
        return -code error $retList
    }

    # copy the token into the calling scope so that the transforms can
    # access it via uplevel, and we can still call cleanup on the real