如何用NASM汇编写一个静态资源服务器?(四)、Content-Length

425 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

前言

前几章我们已经完成了一个基本的Http服务器设计,但缺陷也越写越多,本章来完成最基本的一个头信息设置,也就是Content-Length,如果没有他,虽然可以正常返回,但是浏览器不知道文件具体大小,所以下载文件会无进度。

但是前几章也已经也看到了,代码越写越多,所以,这章我们把新的代码抽离出来单独当在一个文件中,在主文件中通过%include 引入即可。

下面来看下主要代码。

utils.asm
   
getFileSize:
    mov     ecx ,statStructBuffer
    mov     ebx,edx
    mov     eax,195
    int     80h
    mov     eax,[statStructBuffer+11*4]
    ret
loadBaseHeaderToBuffer:
     mov    edi,headersBuffer
     mov    esi,headers
     mov    ecx,72
     rep    movsb 
     ret
setBodyContentLength:
    mov     ecx,10
    mov     edx,0
    mov     ebx,0
loopdiv:
    div     ecx
    push    edx     ;余数入栈
    inc     ebx     ;;存取结果是几位数
    mov     edx,0
    mov     ecx,10
    cmp     eax,0   ;;商是否为0
    jnz     loopdiv
 
setContentLengthAscii:    
    mov     ecx,ebx
    mov     eax,0
_loop:
    nop
    pop     edx     ;;获取第n位
    add     edx,48
    mov     [headersBuffer+72+eax],edx
    inc     eax ;;偏移
    loop     _loop
  
    mov     edi,headersBuffer
    add     edi,72
    add     edi,eax
    mov     esi,header_end
    mov     ecx,4
    rep     movsb
    
    ret

第一个函数getFileSize用来获取文件大小,这需要了解一下Linux中stat结构体以及stat函数,调用stat函数时,传入文件名和stat结构体指针,最终我们要的信息就会被包含在stat结构体中,但是奇怪的是,stat的系统调用号是18,但是当我调用他时,总会返回错误,但是当调用195时候,却是正常的,195的函数名却是stat64,具体原因就不清楚了,而我们要的文件大小信息,会偏移11*4,这个位置以后的4个字节就是我们要的文件大小,所以就有了下面代码。

 mov     eax,[statStructBuffer+11*4]

loadBaseHeaderToBuffer用来加载基本头信息到buffer中,这个buffer用来和以后的文件大小拼接,并返回给客户端。

setBodyContentLength函数和下面所有函数用来向buffer中追加文件大小,你会发现不就是追加个4字节的东西么,为什么会这么长,原因是我们要把一个int,转换成ascii,比如文件大小是11,可不能吧11直接追加到buffer后面,需要把11转换成ascii码,也就是将4949追加到后面,这样才行。

转换的方法就是不停除10,取出最后一位,也就是将余数入栈,最后遍历所有已经入栈的数字,加上48,变成标准的ascii,并拼接到后面。

主代码如下,下面就不说了,可以参考前几章。

%include "/home/HouXinLin/project/nasm/include/io.inc"
%include "utils.asm"

SECTION .data
    headers db 'HTTP/1.1 200 OK', 0Dh, 0Ah,'Content-Type: application/octet-stream',0Dh, 0Ah,'Content-Length:',0h
    header_end db 0Dh, 0Ah, 0Dh, 0Ah,0h
    notfound_response db 'HTTP/1.1 404 OK', 0Dh, 0Ah, 'Content-Type: text/html', 0Dh, 0Ah, 'Content-Length: 10', 0Dh, 0Ah, 0Dh, 0Ah, 'not found!', 0Dh, 0Ah, 0h
    root db '/home/HouXinLin/test', 0h 
    msg  db 'accept',0h
    ok  db 'ok',0h
    fail  db 'fail',0h
    SO_REUSEADDR  db 1,0h
   
    

SECTION .bss
    headersBuffer resb  4096
    fileContents resb   40960
    responseBuffer resb 40960
    requestBuffer resb  4096
    fullPath resb       1024
    requestPath resb    1024
    socketbuf    resb    4
    

    buffer resb 1024
    statStructBuffer resb 144

SECTION .text
global  CMAIN
 
CMAIN:
    mov ebp, esp; for correct debugging

    xor     eax, eax
    xor     ebx, ebx
    xor     edi, edi
    xor     esi, esi

    
socket:
 
    push    byte 6 
    push    byte 1
    push    byte 2
    mov     ecx, esp
    mov     ebx, 1
    mov     eax, 102
    int     80h    ;;创建Socket
   
bind:
    push eax
   
    mov  edi,4
    mov  esi,SO_REUSEADDR
    mov  edx,2
    mov  ecx,1
    mov  ebx,eax
    mov  eax,366
    int  80h
    
    pop eax
    mov     edi, eax ;;edi存放server socket描述副
    
    push    dword 0x00000000
    push    word 0x901f    ;;端口8080
    push    word 2
    mov     ecx, esp
    push    byte 16
    push    ecx
    push    edi
    mov     ecx, esp
    mov     ebx, 2
    mov     eax, 102
    int     80h         ;;绑定8080端口
    cmp     eax,0
    je      listen
    jmp     exit
 
listen:
 
    push    byte 1 
    push    edi
    mov     ecx, esp
    mov     ebx, 4
    mov     eax, 102
    int     80h     ;监听
 
accept:
 
    push    byte 0 
    push    byte 0
    push    edi
    mov     ecx, esp
    mov     ebx, 5
    mov     eax, 102
    int     80h
 
    mov     esi, eax          ;;将客户端描述符保存到esi中


    mov     eax, 2
    int     80h
    cmp     eax, 0
    jz      read
    jmp     accept
    
read:
    mov     edx,6
    mov     ecx,msg
    mov     ebx,1
    mov     eax,4
    int     80h
    
    mov     edx, 4096          ;;读取客户端内容
    mov     ecx, requestBuffer
    mov     ebx, esi
    mov     eax, 3
    int     80h


getRequestResourcePath: ;;获取请求资源路径
    mov     eax,requestBuffer
    mov     ebx,eax
    
nextResourceChar:    
    cmp     byte[ebx],32    ;;如果是空格
    jz      record           ;;开始记录
    inc     ebx             ;;下一个字符
    jmp     nextResourceChar 
    ret
    
record: 
    mov     edx,ebx     ;;ebx是第一个空格后的位置
    sub     edx,eax     ;存放开始索引
    mov     ecx, requestBuffer
    add     ecx,edx         ;;从ecx后的位置开始查看地一个空格
    mov     edx,0
    
hasEnd:
    inc     ecx
    cmp     byte[ecx],32  ;;如果下一个也是空格
    jz      finishSearch
    
    mov     eax, dword[ecx]   ;;获取当前字符
    mov     dword[requestPath+edx],eax
    inc     edx
    jmp     hasEnd
    
finishSearch:
    push    esi
    mov     esi,root
    mov     edi,fullPath
    mov     ecx,20
    rep     movsb   ;;复制root
    
    mov     esi,requestPath 
    mov     edi,fullPath
    add     edi,20
    mov     ecx,edx
    rep     movsb
    pop     esi
   

openFile:
    mov     ecx, 4           ;;打开文件
    mov     ebx, fullPath
    mov     eax, 5
    int     80h
    cmp     eax,0
    jl      notfound
    push    eax         ;;保存文件描述符

write:
    push    eax
    push    esi
    mov     edx,fullPath
    call    getFileSize                 ;获取文件大小,结果保存到eax中
    call    loadBaseHeaderToBuffer      ;家在header buffer用来拼接
    call    setBodyContentLength        ;设置body大小
   
    mov     edx,eax
    add     edx,76      ;其中72个字节是头信息,4个字节是body和header之间的分割符号
    
    pop     esi
    pop     eax

    mov     ecx, headersBuffer    ;;首先输出头
    mov     ebx, esi 
    mov     eax, 4  
    int     80h 
    
hasNext:  
    pop     eax         ;;文件描述符传递给readFile
    push    eax
    call    readFile      ;;调用之后eax保存读取的大小,fileContents保存文件内容
    push    eax
    mov     edx,eax
    mov     ecx, fileContents    ;;输出内容
    mov     ebx, esi 
    mov     eax, 4  
    int     80h 
    pop     eax             ;;读取的文件字节数
    cmp     eax,0
    call    closeFile
    jz      closeSocket
    jmp     hasNext
notfound:
    mov     edx,76
    mov     ecx, notfound_response    ;;输出头
    mov     ebx, esi 
    mov     eax, 4  
    int     80h 
closeSocket:
    ;;关闭客户端socket

    push    2
    push    esi
    mov     ecx, esp
    mov     ebx, 13
    mov     eax, 102
    int     80h
    
    cmp     eax,0
    jz      exit
    
    mov     edx,4
    mov     ecx,fail
    mov     ebx,1
    mov     eax,4
    int     80h
    jmp     exit    
readFile:
    mov     edx, 40960         ;;尝试读取40960个字节到fileContents
    mov     ecx, fileContents  
    mov     ebx, eax 
    mov     eax, 3  
    int     80h                 ;读取    
    ret
closeFile:
    mov     ebx,eax
    mov     eax,6    
    int     80h
    ret
exit:
  
    mov     ebx,0
    mov     eax,1
    int     80h