HTTP Helper: Easy W5100 Interface

┌───────────────────────────────────────┐
│             HTTP Helper               │
│         Author: Phil Pilgrim          │
│(c) Copyright 2013 Bueno Systems, Inc. │
│   See end of file for terms of use.   │
└───────────────────────────────────────┘

This object provides an easy-to-use wrapper for Parallax's W5100_Indirect_Driver.

Revision History

2013.10.04: v1.0 Initial release.

Contact Information

Phil Pilgrim: propeller@phipi.com

Introduction

This object provides a wrapper for the W5100_Indirect_Driver as used on Parallax's Spinneret ethernet module. It enables the easy retrieval of client requests with their parsed-out parameters, along with formatting and buffering for the HTTP responses.

Constants


SOURCE CODE...
  BUF_SIZE      = 512

  LF            = $0a
  CR            = $0d
  QU            = $22
  
  #0, W5100_DATA0,W5100_DATA1,W5100_DATA2,W5100_DATA3,W5100_DATA4,W5100_DATA5,W5100_DATA6,W5100_DATA7 {
   }, W5100_ADDR0,W5100_ADDR1,W5100_WR   ,W5100_RD   ,W5100_CS   ,W5100_INT  ,W5100_RST  ,W5100_SEN

  BUFFER_SIZE   = 2048


Instance Variables


SOURCE CODE...
  byte  macaddr[6], data[BUFFER_SIZE]
  byte  buffer[BUF_SIZE]
  byte  nparams
  word  port, params[33], bufptr
  long  gateway, subnet, ip, client, timeout


External Objects


SOURCE CODE...
  w5100 : "W5100_Indirect_Driver.spin"


Public Spin Methods

Start and Stop

Start and stop the HTTP server.

start(_ip, _gateway, _subnet, _mac)

Start the HTTP server.

Parameters:
_ip: pointer to a string containing the IPV4 address of the server
_gateway: pointer to a string containing the IPV4 address of the internet gateway
_subnet: pointer to a string containing the IPV4 subnet designator
_mac: MAC address of the server hardware
Return: port number, as contained in _ip or 80 (default)
Example: srv.start(string("192.168.0.50:3456"), string("192.168.0.4"), string("255.255.255.0"), string("00 08 DC 16 F0 13"))
Start the server at IP location 192.168.0.50 and port 3456. Internet gateway is at 192.168.0.4. Subnet is 255.255.255.0. Hardware MAC address is 00 08 DC 16 F0 13. Return value is 3456 (port number).

SOURCE CODE...
PUB start(_ip, _gateway, _subnet, _mac) | i, digit, part

  stop
  port := 0
  ip := parse_ip(_ip)
  ifnot (port)
    port := 80
  subnet := parse_ip(_subnet)
  gateway := parse_ip(_gateway)
  client := 0
  
  i~
  repeat
    case digit := byte[_mac++]
      "0" .. "9" : digit -= "0"
      "A" .. "F" : digit += 10 - "A"
      "a" .. "f" : digit += 10 - "a"
      other      : digit~~
    if (digit => 0)
      part := part << 4 | digit
      if (i++ & 1)
        macaddr[(i - 1) >> 1] := part
  until i == 12

  w5100.StartINDIRECT(W5100_DATA0, W5100_ADDR0, W5100_ADDR1, W5100_CS, W5100_RD, W5100_WR, W5100_RST, W5100_SEN)
  w5100.InitAddresses(true, @macaddr, @gateway, @subnet, @ip)
  socket_open
  timeout := clkfreq * 5
  return port


stop

Stop the HTTP server.

Return: none
Example: srv.stop
Stop the HTTP server.

SOURCE CODE...
PUB stop

  w5100.StopINDIRECT


Request handling

Process incoming client requests.

echo_request

Echo the client request as a plain text page.

Return: none
Example: srv.echo_request
Wait for a request from the client and return it as a plain page.

SOURCE CODE...
PUB echo_request | req

  repeat until get_request
  begin_plain_page
  str(@data)
  end_plain_page  


request

Wait for a request, parse the address line, and return a pointer to the parsed address (sans ? arguments).

Return: pointer to the parsed address.
Example: req := srv.request
Wait for a client request, parse it, and return a pointer to the address portion.

SOURCE CODE...
PUB request

  repeat until get_request
  return parse(@data)


request0

Poll for a request. If one has come in, parse the address line, and return a pointer to the parsed address (sans ? arguments). If no request, return zero.

Return: pointer to the parsed address, or zero if no request.
Example: if (req := srv.request0)
Assign the parsed address pointer to req. If there was a request, proceed with the if statement's consequent.

SOURCE CODE...
PUB request0

  if (get_request)
    return parse(@data)
  else
    return false


param(straddr)

Return the value of a chosen parameter from a processed GET request.

Parameters:
straddr: pointer to a string containing the parameter name.
Return: pointer to a string containing the value of the parameter.
Example: value_addr := srv.param(string("name"))
Return a pointer to a sting containing the value of the name parameter in the request. For example, if the request line were /index?name=Sam&phone=555-1234, the call would return the address of a string containing "Sam".

SOURCE CODE...
PUB param(straddr) | i

  repeat i from 1 to nparams - 1 step 2
    if strcomp(straddr, params[i])
      return params[i + 1]
  return 0    


Page sending

Send response pages in various formats.

begin_plain_page

Begin a plain text page, i.e. buffer the HTTP header for a text/plain response.

Return: none
Example: srv.begin_plain_page
Begin a plain text page. Content is expected to follow.

SOURCE CODE...
PUB begin_plain_page

  _http
  str(@plain_http)


begin_custom_content

Begin a page with custom (non-HTML, non-text/plain) content.

Return: none
Example: srv.begin_custom_content
Buffer a simple two-line HTTP header, including "200 OK". "Content-type" header line and actual content is expected to follow.

SOURCE CODE...
PUB begin_custom_content

  _http


begin_plain_html_page(title, refresh, background)

Start an HTML page as plain text. (Useful for debugging.)

Parameters:
title: Page title.
refresh: Time is seconds for automatic refresh, or zero for no refresh.
background: Pointer to a string containing the page background color, or zero for default white.
Return: none
Example: srv.begin_plain_html_page(string("My Page"), 2, string("red"))
Start an HTML page as plain text which would otherwise have the title "My Page", refresh every two seconds, and have a red background.

SOURCE CODE...
PUB begin_plain_html_page(title, refresh, background)

  _http
  str(@plain_http)
  _html_page(title, refresh, background)    


begin_html_page(title, refresh, background)

Start an HTML page, including the <html> and <body> tags.

Parameters:
title: Page title.
refresh: Time is seconds for automatic refresh, or zero for no refresh.
background: Pointer to a string containing the page background color, or zero for default white.
Return: none
Example: srv.begin_html_page(string("My Page"), 0, string("#ffff00"))
Start an HTML page having the title "My Page", with no automatic refresh, and with a yellow background.

SOURCE CODE...
PUB begin_html_page(title, refresh, background)

  _http
  str(@html_http)
  _html_page(title, refresh, background)


not_found

Send a simple, plain-text "404 Not Found" page.

Return: none
Example: srv.not_found
Send a "404 Not Found" page.

SOURCE CODE...
PUB not_found

  str(@error_404)
  end_all  


end_plain_page

End and send a plain text page after the content has been buffered.

Return: none
Example: srv.end_plain_page
End and send the buffered plain text page.

SOURCE CODE...
PUB end_plain_page

  end_all


end_custom_content

End and send custom content after the content has been buffered.

Return: none
Example: srv.end_custom_content
End and send the buffered custom content.

SOURCE CODE...
PUB end_custom_content

  end_all


end_html_page

End and send an HTML page after the content has been buffered. Includes the </body> and </html> tags.

Return: What gets returned, or 'none'.
Example: srv.end_html_page
End and send the buffered HTML page.

SOURCE CODE...
PUB end_html_page

  newline
  str(string("  </body>", 10, "</html>", 10))
  end_all


str(straddr)

Buffer for sending to the client the zero-terminated string pointed to by the parameter.

Parameters:
straddr: pointer to a zero-terminated string of data to send to the client.
Return: none
Example: srv.str(@index_page)
Buffer the string located at address @index_page.

SOURCE CODE...
PUB str(straddr)

  array(straddr, strsize(straddr))


outnz(char)

Buffer a single character for sending to the client, but only if it's non-zero.

Parameters:
char: the character to send
Return: none
Example: srv.outnz(next_char)
Buffer the character next_char unless next_char == 0.

SOURCE CODE...
PUB outnz(char)

  if (char)
    out(char)


out(char)

Buffer a single character to send to the client.

Parameters:
char: the character to send
Return: none
Example: srv.out(srv#LF)
Buffer a linefeed ($0a).

SOURCE CODE...
PUB out(char)

  array(@char, 1)


array(addr, count)

Buffer an entire byte array of characters to send to the client.

Parameters:
addr: address of the array's first element.
count: the number of bytes to buffer, beginning at addr.
Return: none
Example: srv.array(@data, 16)
Buffer 16 bytes of data beginning at address @data.

SOURCE CODE...
PUB array(addr, count)

  if (bufptr + count > BUF_SIZE)
    flush
  if (bufptr + count > BUF_SIZE)
    w5100.txTCP(0, addr, count)
  else
    bytemove(@buffer + bufptr, addr, count)
    bufptr += count 


Private Spin Methods


get_request

Check status and retrieve a request if one is available. REturn the packet size, or zero for no request.

SOURCE CODE...
PRI get_request : packet_size | time

  ifnot w5100.SocketTCPestablished(0)
    return
  ifnot (lookdown(get_status : $14, $16, $17, $11))
    socket_reset
    return
  time := cnt
  repeat until (cnt - time > timeout)
    if (packet_size := W5100.rxTCP(0, @data))
      data[packet_size]~
      return
  socket_reset
  return


get_status

Return the current server status.

Return: What gets returned, or 'none'.
Example: Sample method call.
Explain what the sample call does. Next lines, if needed...

SOURCE CODE...
PRI get_status : status

  w5100.readIND(w5100#_S0_SR, @status, 1)


parse(dataddr)

Parse the request line, replacing quoted characters with their ASCII equivalants and separating out GET parameter into their key/value pairs.

SOURCE CODE...
PRI parse(dataddr) | char, i, getptr, putptr, varend

  nparams~
  repeat while ((char := byte[dataddr++]) and char <> LF and char <> CR)
    if(lookdown(char : " ", "?", "&", "="))
      byte[dataddr - 1]~
      params[nparams++] := dataddr
  repeat i from 0 to nparams - 1
    getptr := putptr := params[i]
    varend := getptr + strsize(getptr)
    repeat while getptr < varend
      case char := byte[getptr++]
        "+": byte[putptr++] := " "
        "%":
          repeat 2
            char := char << 4 | lookdownz(byte[getptr++] : "0" .. "9", "A" .. "F")
          byte[putptr++] := char 
        other: byte[putptr++] := char
    byte[putptr]~
  return params[0]


end_all

Flush the buffer into the server, send the TCP packet, close the socket, then reopen it.

SOURCE CODE...
PRI end_all

  flush
  w5100.txTCP(0, 0, 0)
  socket_close    
  socket_open


_http

Buffer the beginning HTTP header.

SOURCE CODE...
PRI _http

  bufptr~
  str(@http)


_html_page(title, refresh, background)

Buffer the HTML page header.

SOURCE CODE...
PRI _html_page(title, refresh, background)

  str(@html_head)
  if (title)
    str(string(LF, "    <title>"))
    str(title)
    str(string("</title>"))
  if (refresh)
    str(string(LF, "    <meta http-equiv=",QU,"refresh",QU," content=",QU))
    str(refresh)
    str(string(QU,">"))
  str(@html_body)
  if (background)
    str(string(" style=",QU,"background-color: "))
    str(background)
    out(QU)
  str(string(">"))
  newline


newline

Buffer a linefeed.

SOURCE CODE...
PRI newline

  out(LF)


flush

Flush the local buffer into the W5100 server.

SOURCE CODE...
PRI flush

  if (bufptr)
    w5100.txTCP(0, @buffer, bufptr)
    bufptr~


parse_ip(_ip)

Parse a stringified IP address into its 32-bit long equivalent.

SOURCE CODE...
PRI parse_ip(_ip) : retip | part, digit, char

  part~
  repeat
    if ((char := byte[_ip++]) => "0" and char =< "9")
      part := part * 10 + char - "0"
    else
      retip := retip >> 8 | (part <# 255) << 24
      part~
      if (char == 0 or char == ":")
        quit
  if (char == ":")
    repeat while (byte[_ip] => "0" and byte[_ip] =< "9")
      port := port * 10 + byte[_ip++] - "0"


socket_reset

Close, then open the socket.

SOURCE CODE...
PRI socket_reset

  socket_close
  socket_open


socket_open

Open a socket for listening.

SOURCE CODE...
PRI socket_open

  w5100.SocketOpen(0, W5100#_TCPPROTO, port, port, @client)
  waitcnt(clkfreq / 4 + cnt)
  w5100.SocketTCPlisten(0)
  waitcnt(clkfreq / 4 + cnt)


socket_close

Close the open socket.

SOURCE CODE...
PRI socket_close

  w5100.SocketTCPdisconnect(0)
  waitcnt(clkfreq / 40 + cnt)
  w5100.SocketClose(0)


Constant Strings


SOURCE CODE...
http          byte  "HTTP/1.1 200 OK",10                'Initial HTTP header for successful request.
              byte  "Connection: close",10
              byte  0

error_404     byte  "HTTP/1.1 404 Not Found",10         'HTTP header for "content not found."
              byte  "Connection: close",10
              byte  "Content-type: text/plain",10,10
              byte  "404 Not Found."
              byte  0
              
plain_http    byte  "Content-type: text/plain",10,10    'HTTP headr for plain text.
              byte  0

html_http     byte  "Content-type: text/html",10,10     'HTTP header for HTML.
              byte  0
              
html_head     byte  "<html xmlns=",QU,"http://www.w3.org/1999/xhtml",QU," dir=",QU,"ltr",QU," lang=",QU,"en",QU,">",10          'Beginning of HTML page.
              byte  "  <head>",10
              byte  "    <meta http-equiv=",QU,"Content-Type",QU," content=",QU,"text/html; charset=ISO-8859-1",QU," />"
              byte  0
              
html_body     byte  10,"  </head>",10                   'Beginning of HTML body.
              byte  "  <body "
              byte  0


License

┌──────────────────────────────────────────────────────────────────────────────────────┐
│                            TERMS OF USE: MIT License                                 │                                                            
├──────────────────────────────────────────────────────────────────────────────────────┤
│Permission is hereby granted, free of charge, to any person obtaining a copy of this  │
│software and associated documentation files (the "Software"), to deal in the Software │
│without restriction, including without limitation the rights to use, copy, modify,    │
│merge, publish, distribute, sublicense, and/or sell copies of the Software, and to    │
│permit persons to whom the Software is furnished to do so, subject to the following   │
│conditions:                                                                           │
│                                                                                      │
│The above copyright notice and this permission notice shall be included in all copies │
│or substantial portions of the Software.                                              │
│                                                                                      │
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,   │
│INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A         │
│PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT    │
│HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF  │
│CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE  │
│OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                                         │
└──────────────────────────────────────────────────────────────────────────────────────┘