summary refs log tree commit diff stats
path: root/tests/cpp/tterminate_handler.nim
blob: c5ccef53c7de75a0f2379077c84a7fee2c7d89b1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
discard """
  targets: "cpp"
  outputsub: "Error: unhandled unknown cpp exception"
  exitcode: 1
  disabled: true
"""
type Crap {.importcpp: "int".} = object

var c: Crap
raise c
ding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#
#
#            Nimrod's Runtime Library
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module implements helper procs for SCGI applications. Example:
## 
## .. code-block:: Nimrod
##
##    import strtabs, sockets, scgi
##
##    var counter = 0
##    proc handleRequest(client: TSocket, input: string, 
##                       headers: PStringTable): bool {.procvar.} =
##      inc(counter)
##      client.writeStatusOkTextContent()
##      client.send("Hello for the $#th time." % $counter & "\c\L")
##      return false # do not stop processing
##
##    run(handleRequest)
##

import sockets, strutils, os, strtabs, asyncio

type
  EScgi* = object of EIO ## the exception that is raised, if a SCGI error occurs

proc scgiError*(msg: string) {.noreturn.} = 
  ## raises an EScgi exception with message `msg`.
  var e: ref EScgi
  new(e)
  e.msg = msg
  raise e

proc parseWord(inp: string, outp: var string, start: int): int = 
  result = start
  while inp[result] != '\0': inc(result)
  outp = substr(inp, start, result-1)

proc parseHeaders(s: string, L: int): PStringTable = 
  result = newStringTable()
  var i = 0
  while i < L:
    var key, val: string
    i = parseWord(s, key, i)+1
    i = parseWord(s, val, i)+1
    result[key] = val
  if s[i] == ',': inc(i)
  else: scgiError("',' after netstring expected")
  
proc recvChar(s: TSocket): char = 
  var c: char
  if recv(s, addr(c), sizeof(c)) == sizeof(c): 
    result = c
  
type
  TScgiState* = object of TObject ## SCGI state object
    server: TSocket
    bufLen: int
    client*: TSocket ## the client socket to send data to
    headers*: PStringTable ## the parsed headers
    input*: string  ## the input buffer
  
  TAsyncScgiState* = object of TScgiState
    handleRequest: proc (server: var TAsyncScgiState, client: TSocket, 
                         input: string, headers: PStringTable,
                         userArg: PObject) {.nimcall.}
    userArg: PObject
  PAsyncScgiState* = ref TAsyncScgiState
    
proc recvBuffer(s: var TScgiState, L: int) =
  if L > s.bufLen: 
    s.bufLen = L
    s.input = newString(L)
  if L > 0 and recv(s.client, cstring(s.input), L) != L: 
    scgiError("could not read all data")
  setLen(s.input, L)
  
proc open*(s: var TScgiState, port = TPort(4000), address = "127.0.0.1") = 
  ## opens a connection.
  s.bufLen = 4000
  s.input = newString(s.buflen) # will be reused
  
  s.server = socket()
  new(s.client) # Initialise s.client for `next`
  if s.server == InvalidSocket: scgiError("could not open socket")
  #s.server.connect(connectionName, port)
  bindAddr(s.server, port, address)
  listen(s.server)
  
proc close*(s: var TScgiState) = 
  ## closes the connection.
  s.server.close()

proc next*(s: var TScgistate, timeout: int = -1): bool = 
  ## proceed to the first/next request. Waits ``timeout`` miliseconds for a
  ## request, if ``timeout`` is `-1` then this function will never time out.
  ## Returns `True` if a new request has been processed.
  var rsocks = @[s.server]
  if select(rsocks, timeout) == 1 and rsocks.len == 0:
    accept(s.server, s.client)
    var L = 0
    while true:
      var d = s.client.recvChar()
      if d == '\0':
        s.client.close()
        return false
      if d notin strutils.digits: 
        if d != ':': scgiError("':' after length expected")
        break
      L = L * 10 + ord(d) - ord('0')  
    recvBuffer(s, L+1)
    s.headers = parseHeaders(s.input, L)
    if s.headers["SCGI"] != "1": scgiError("SCGI Version 1 expected")
    L = parseInt(s.headers["CONTENT_LENGTH"])
    recvBuffer(s, L)
    return True
  
proc writeStatusOkTextContent*(c: TSocket, contentType = "text/html") = 
  ## sends the following string to the socket `c`::
  ##
  ##   Status: 200 OK\r\LContent-Type: text/html\r\L\r\L
  ##
  ## You should send this before sending your HTML page, for example.
  c.send("Status: 200 OK\r\L" &
         "Content-Type: $1\r\L\r\L" % contentType)

proc run*(handleRequest: proc (client: TSocket, input: string, 
                               headers: PStringTable): bool {.nimcall.},
          port = TPort(4000)) = 
  ## encapsulates the SCGI object and main loop.
  var s: TScgiState
  s.open(port)
  var stop = false
  while not stop:
    if next(s):
      stop = handleRequest(s.client, s.input, s.headers)
      s.client.close()
  s.close()

proc open*(handleRequest: proc (server: var TAsyncScgiState, client: TSocket, 
                                input: string, headers: PStringTable,
                                userArg: PObject) {.nimcall.},
           port = TPort(4000), address = "127.0.0.1",
           userArg: PObject = nil): PAsyncScgiState =
  ## Alternative of ``open`` for asyncio compatible SCGI.
  new(result)
  open(result[], port, address)
  result.handleRequest = handleRequest
  result.userArg = userArg

proc getSocket(h: PObject): tuple[info: TInfo, sock: TSocket] =
  var s = PAsyncScgiState(h)
  return (SockListening, s.server)

proc handleAccept(h: PObject) =
  var s = PAsyncScgiState(h)
  
  accept(s.server, s.client)
  var L = 0
  while true:
    var d = s.client.recvChar()
    if d == '\0':
      # Disconnected
      s.client.close()
      return
    if d notin strutils.digits: 
      if d != ':': scgiError("':' after length expected")
      break
    L = L * 10 + ord(d) - ord('0')  
  recvBuffer(s[], L+1)
  s.headers = parseHeaders(s.input, L)
  if s.headers["SCGI"] != "1": scgiError("SCGI Version 1 expected")
  L = parseInt(s.headers["CONTENT_LENGTH"])
  recvBuffer(s[], L)

  s.handleRequest(s[], s.client, s.input, s.headers, s.userArg)

proc register*(d: PDispatcher, s: PAsyncScgiState) =
  ## Registers ``s`` with dispatcher ``d``.
  var dele = newDelegate()
  dele.deleVal = s
  dele.getSocket = getSocket
  dele.handleAccept = handleAccept
  d.register(dele)

when false:
  var counter = 0
  proc handleRequest(client: TSocket, input: string, 
                     headers: PStringTable): bool {.procvar.} =
    inc(counter)
    client.writeStatusOkTextContent()
    client.send("Hello for the $#th time." % $counter & "\c\L")
    return false # do not stop processing

  run(handleRequest)