ip65 technical reference

File : ip65/httpd.s

a simple HTTP server
to use - call httpd_start with AX pointing at routine to call for each inbound page

functions

functiondescription
httpd_start
start a HTTP server
this routine will stay in an endless loop that is broken only if user press the ABORT key (runstop on a c64)
inputs: 
httpd_port_number=port number to listen on
AX = pointer to routine to callback for each inbound HTTP request=
set this to $0000 to use the default handler (which will look for requested files on the local disk).
outputs:
 none

implementation

;a simple HTTP server
;to use - call httpd_start with AX pointing at routine to call for each inbound page
.include "../inc/common.i"

.ifndef KPR_API_VERSION_NUMBER
  .define EQU     =
  .include "../inc/kipper_constants.i"
.endif


HTTPD_TIMEOUT_SECONDS=5 ;what's the maximum time we let 1 connection be open for?


.export httpd_start

.import http_parse_request
.import http_get_value
.import tcp_listen
.import tcp_callback
.import ip65_process
.import check_for_abort_key
.import ip65_error
.import parse_hex_digits
.import print
.import copymem
.importzp copy_src
.importzp copy_dest
.import tcp_inbound_data_ptr
.import tcp_inbound_data_length
.import tcp_send_data_len
.import tcp_send
.import tcp_close
.import io_read_catalogue
.import native_to_ascii
.import io_read_file_with_callback
.import io_filename
.import io_callback
.import timer_seconds
.import  __HTTP_VARS_LOAD__
.import  __HTTP_VARS_RUN__
.import  __HTTP_VARS_SIZE__

temp_ptr =copy_src

.bss
found_eol: .byte 0
connection_closed: .byte 0
output_buffer_length: .res 2
sent_header: .res 1
connection_timeout_seconds: .byte 0
tcp_buffer_ptr: .res 2
buffer_size: .res 1
skip_mode: .res 1
temp_x: .res 1

.segment "HTTP_VARS"

httpd_io_buffer: .word $2000 ;by default, use a 3k buffer at $2000 for storing inbound requests.
httpd_scratch_buffer: .word $2B00 ;by default, use a 1k buffer at $2b00 as a scratchpad
httpd_port_number: .word 80


jump_to_callback:
  jmp $ffff

jump_to_embedded_routine:
  jmp $ffff

get_next_byte:
  lda $ffff
  inc get_next_byte+1
  bne @skip
  inc get_next_byte+2
@skip:
  rts

  
emit_a:
  stx temp_x
  ldx skip_mode
  bne skip_emit_a
emit_a_ptr:
  sta $ffff
  inc emit_a_ptr+1
  bne :+
  inc emit_a_ptr+2
:
  inc output_buffer_length
  bne :+
  inc output_buffer_length+1
  lda output_buffer_length+1
  cmp #2
  bne :+
  jsr send_buffer    
:  
skip_emit_a:
  ldx temp_x
  rts

.code

;start a HTTP server
;this routine will stay in an endless loop that is broken only if user press the ABORT key (runstop on a c64)
;inputs: 
;httpd_port_number=port number to listen on
;AX = pointer to routine to callback for each inbound HTTP request=
;set this to $0000 to use the default handler (which will look for requested files on the local disk).
;outputs:
; none
httpd_start:
  pha ;save AX while we relocate self modifying code
  txa
  pha
  
  ;relocate the self-modifying code 
  ldax #__HTTP_VARS_LOAD__
  stax copy_src
  ldax #__HTTP_VARS_RUN__
  stax copy_dest
  ldax #__HTTP_VARS_SIZE__
  jsr copymem
  pla
  tax
  pla
  
  stx jump_to_callback+2
  bne @not_default_handler
  ldax #default_httpd_handler
  jmp httpd_start
@not_default_handler:
  sta jump_to_callback+1



@listen:
  jsr tcp_close
  ldax httpd_io_buffer
  stax tcp_buffer_ptr 
  ldax #http_callback
  stax tcp_callback
  ldax httpd_port_number
  
  jsr tcp_listen
  bcc @connect_ok
  rts 
@connect_ok: 

  lda #0
  sta connection_closed
  sta found_eol
  clc
  jsr timer_seconds  ;time of day clock: seconds (in BCD)
  sed
  adc #HTTPD_TIMEOUT_SECONDS
  cmp #$60
  bcc @timeout_set
  sec
  sbc #$60
@timeout_set:  
  cld
  sta connection_timeout_seconds

@main_polling_loop:
  jsr ip65_process
  jsr check_for_abort_key
  bcc @no_abort
  lda #KPR_ERROR_ABORTED_BY_USER
  sta ip65_error
  rts
@no_abort:  
  lda found_eol
  bne @got_eol  

  jsr timer_seconds  ;time of day clock: seconds
  
  cmp connection_timeout_seconds  
  beq @connection_timed_out
  lda connection_closed
  beq  @main_polling_loop  
@connection_timed_out:
  jmp @listen
  
@got_eol:
  ldax httpd_io_buffer  
  jsr http_parse_request
  jsr jump_to_callback  ;call the handler to generate the response for this request.
  ;AX should now point at data to be sent
  ;Y should contain the content type/status code
  bcs :+    ;carry is set if the callback routine already sent the response
  jsr send_response
  :


  jmp @listen ;go listen for the next request
  

http_callback:  
  lda tcp_inbound_data_length+1
  cmp #$ff
  bne @not_eof
  inc connection_closed
@done:  
  rts
@not_eof:
  lda found_eol
  bne @done
  
;copy this chunk to our input buffer
  ldax tcp_buffer_ptr  
  stax copy_dest
  ldax tcp_inbound_data_ptr
  stax copy_src
  ldax tcp_inbound_data_length
  jsr copymem

;increment the pointer into the input buffer  
  clc
  lda tcp_buffer_ptr
  adc tcp_inbound_data_length
  sta tcp_buffer_ptr
  sta temp_ptr
  lda tcp_buffer_ptr+1
  adc tcp_inbound_data_length+1
  sta tcp_buffer_ptr+1  
  sta temp_ptr+1
  
;put a null byte at the end (assumes we have set temp_ptr already)
  lda #0
  tay
  sta (temp_ptr),y
    
;look for CR or LF in input
  sta found_eol
  ldax httpd_io_buffer
  stax get_next_byte+1

@look_for_eol:
  jsr get_next_byte
  cmp #$0a
  beq @found_eol    
  cmp #$0d
  bne @not_eol
@found_eol:
  inc found_eol
  rts
@not_eol:
  cmp #0
  bne @look_for_eol 
  rts
  
  
default_httpd_handler:
  lda #$02
  jsr http_get_value
  bcc @not_error
  ldy #5
  clc
  rts
@not_error:  
  stax temp_ptr
  ldy #1
  lda (temp_ptr),y
  bne @not_index
  ldax #default_html
  ldy #2
  clc
  rts
@not_index:
  ;assume that 'path' is a filename  
  inc temp_ptr
  bne :+
  inc temp_ptr
:
  lda #0
  sta sent_header
  ldax temp_ptr
  stax io_filename
  ldax #send_file_callback
  stax io_callback
  ldax httpd_scratch_buffer
  jsr io_read_file_with_callback
  bcc @sent_ok
  ldy #4
  clc  
  rts
@sent_ok:
  sec
  rts

send_file_callback:
  sty buffer_size             ;only 1 (the last) sector can ever be !=$100 bytes
  lda  sent_header
  bne @sent_header
  ldy #3  ;"application/octet-stream"
  jsr send_header
  inc sent_header
@sent_header:
  ldax  httpd_scratch_buffer
  stax  temp_ptr
  ldy #0
@loop:
  tya
  pha
  lda (temp_ptr),y
  jsr emit_a
  pla
  tay
  iny
  cpy buffer_size
  bne @loop
  rts

reset_output_buffer:
  ldax httpd_io_buffer  
  sta emit_a_ptr+1
  stx emit_a_ptr+2
  lda #0
  sta output_buffer_length
  sta output_buffer_length+1
  sta skip_mode
  rts


emit_disk_catalogue:

  ldax httpd_scratch_buffer
  jsr  io_read_catalogue
  lda get_next_byte+1
  pha
  lda get_next_byte+2
  pha
  ldax  httpd_scratch_buffer
  stax  get_next_byte+1

@next_filename:

  jsr get_next_byte
  cmp #0
  beq  @done
  pha
  
  
  ldax #file_li_preamble
  jsr  emit_string
  pla
  pha
  jsr  emit_a  
  ldax get_next_byte+1  
  jsr  emit_string

  ldax #file_li_middle
  jsr  emit_string
  pla
  bne @convert_byte
@next_byte:
  jsr get_next_byte
  cmp #0
  beq  @done_this_filename
@convert_byte:  
  jsr native_to_ascii
  jsr  emit_a
  jmp @next_byte
@done_this_filename:


  

  ldax #file_li_postamble
  jsr  emit_string


  jmp  @next_filename
@done:  
  
  pla
  sta get_next_byte+2
  pla
  sta get_next_byte+1
  rts
  
send_response:
  stax get_next_byte+1
  jsr reset_output_buffer
  jsr send_header  
  
@response_loop:
  jsr get_next_byte
  cmp #0
  bne @not_last_byte
@send_buffer:  
  jmp send_buffer
@not_last_byte:  
  cmp #'%'
  beq @escape
@back_from_escape:
  jsr emit_a
  jmp @response_loop

@escape:
  jsr get_next_byte
  cmp #0
  beq @send_buffer
  cmp #'%'
  beq @back_from_escape
  cmp #'$'
  bne :+
  jsr get_next_byte
  jsr http_get_value
  bcs @response_loop
  jsr emit_string
  jmp @response_loop
:  
  cmp #'.'
  bne :+
  lda #0
  sta skip_mode
  jmp @response_loop
:
  cmp #'?'
  bne :+
  lda #0
  sta skip_mode
  jsr get_next_byte
  jsr http_get_value
  bcc @response_loop
  inc skip_mode
  jmp @response_loop
:  
  cmp #'!'
  bne :+
  lda #0
  sta skip_mode
  jsr get_next_byte
  jsr http_get_value
  bcs @response_loop
  inc skip_mode
  jmp @response_loop
:  
 
  cmp #';'
  bne :+
  jsr get_next_byte
  sta jump_to_embedded_routine+1
  jsr get_next_byte
  sta jump_to_embedded_routine+2
  jmp @call_embedded_routine
  :
  cmp #':'
  bne :+
  jsr @get_next_hex_value
  sta jump_to_embedded_routine+2    
  jsr @get_next_hex_value
  sta jump_to_embedded_routine+1
@call_embedded_routine:  
  lda skip_mode
  bne @dont_call_embedded_routine
  ldax #emit_a
  jsr jump_to_embedded_routine
@dont_call_embedded_routine:  
  jmp @response_loop
  :
;if we got here, it's an invalid escape code  
  jmp @response_loop
  
@get_next_hex_value:
  jsr get_next_byte
  tax
  jsr get_next_byte
  jmp parse_hex_digits
    
send_buffer:    
  ldax output_buffer_length
  stax tcp_send_data_len
  ldax httpd_io_buffer  
  jsr tcp_send
  jmp reset_output_buffer
 
 send_header:
;inputs: Y = header type
;$00 = no header (assume header sent already)
;$01 = 200 OK, 'text/text'
;$02 = 200 OK, 'text/html'
;$03 = 200 OK, 'application/octet-stream' 
;$04 = 404 Not Found
;$05..$FF = 500 System Error

  cpy #00
  bne :+
  rts
:
  cpy #1
  bne  @not_text
  jsr  emit_ok_status_line_and_content_type
  ldax  #text_text
  jsr  emit_string
  jmp  @done

@not_text:
  cpy #2
  
  bne  @not_html
  jsr  emit_ok_status_line_and_content_type
  ldax  #text_html
  jsr  emit_string
  jmp  @done

@not_html:
  cpy #3
  bne  @not_binary
  jsr  emit_ok_status_line_and_content_type
  ldax  #application_octet_stream
  jsr  emit_string
  jmp  @done

@not_binary:
  cpy #4
  bne  @not_404
  ldax  #http_version
  jsr  emit_string
  ldax  #status_not_found
  jsr  emit_string

  jsr @done
  ldax #status_not_found
  jmp emit_string
  
@not_404:
  ldax  #http_version
  jsr  emit_string
  ldax  #status_system_error
  jsr  emit_string
  jsr @done
  ldax #status_system_error
  jmp emit_string
@done:
  ldax   #end_of_header
  jmp  emit_string      


emit_ok_status_line_and_content_type:
  ldax  #http_version
  jsr  emit_string
  ldax  #status_ok
  jsr  emit_string
  ldax  #content_type
  jmp  emit_string    

emit_string:
  stax  temp_ptr
  ldy  #0
@next_byte:
  lda  (temp_ptr),y
  beq  @done
  jsr  emit_a
  iny
  bne  @next_byte
@done:
  rts
  
 
.rodata
default_html:
.byte "

Index of /


kipper - the 100%% 6502 m/l web server " .byte 0 CR=$0D LF=$0A http_version: .byte "HTTP/1.0 ",0 status_ok: .byte "200 OK",CR,LF,0 status_not_found: .byte "404 Not Found",CR,LF,0 status_system_error: .byte "500 System Error",CR,LF,0 content_type: .byte "Content-Type: ",0 text_text: .byte "text/text",CR,LF,0 text_html: .byte "text/html",CR,LF,0 application_octet_stream: .byte "application/octet-stream",CR,LF,0 end_of_header: .byte "Connection: Close",CR,LF .byte "Server: Kipper_httpd/0.c64",CR,LF .byte CR,LF,0 file_li_preamble: .byte "
  • ",0 file_li_postamble: .byte "
  • ",0 ;-- LICENSE FOR httpd.s -- ; The contents of this file are subject to the Mozilla Public License ; Version 1.1 (the "License"); you may not use this file except in ; compliance with the License. You may obtain a copy of the License at ; http://www.mozilla.org/MPL/ ; ; Software distributed under the License is distributed on an "AS IS" ; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the ; License for the specific language governing rights and limitations ; under the License. ; ; The Original Code is ip65. ; ; The Initial Developer of the Original Code is Jonno Downes, ; jonno@jamtronix.com. ; Portions created by the Initial Developer are Copyright (C) 2009 ; Jonno Downes. All Rights Reserved. ; -- LICENSE END --