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

749 阅读5分钟

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

前言

Content-Type这个头还是比较重要的,在上一章中,我们只能控制浏览器进行下载,而当遇到png、html的文件时,我们希望浏览器进行展示,这部分就是通过Content-Type来控制的,当他的值是application/octet-stream时,浏览器会进行下载,而当是其他类型时,浏览器则根据不同类型,做出不同响应。

这里代码变的逐渐多了起来,我已经上传到github,链接如下

github

在汇编中,操作这部分还是比较繁琐的,首先我们需要定义一个结构体类型,类似于c中的struct,每个结构体中有一个key和value,key表示后缀名,value是这个后缀名所响应的mime类型,并把所有的实例放在一起,在NASM中,可以如下操作。

struc Media_Item
    .key    resb   10
    .value  resb   20
endstruc

SECTION .data
    HTML_MEDIA: ISTRUC Media_Item
        AT Media_Item.key, db 'html'
        AT Media_Item.value, db 'text/html'
    IEND
    
    IMAGE_PNG_MEDIA: ISTRUC Media_Item
        AT Media_Item.key, db 'png'
        AT Media_Item.value, db 'image/png'
    IEND
    
    IMAGE_JPG_MEDIA: ISTRUC Media_Item
        AT Media_Item.key, db 'jpg'
        AT Media_Item.value, db 'image/jpg'
    IEND
    
    IMAGE_CSS_MEDIA: ISTRUC Media_Item
        AT Media_Item.key, db 'css'
        AT Media_Item.value, db 'text/css'
    IEND
    IMAGE_JS_MEDIA: ISTRUC Media_Item
        AT Media_Item.key, db 'js'
        AT Media_Item.value, db 'text/javascript'
    IEND    
    MediaArray dd HTML_MEDIA,0h,IMAGE_PNG_MEDIA,0h,IMAGE_JPG_MEDIA,IMAGE_CSS_MEDIA,IMAGE_JS_MEDIA,0h
    
    MediaArraySize dd 5

我们规定,一个key是10字节,value是20字节,(虽然用不了这么多),接着把他们放在一起,形成MediaArray,那么如果要获取第2个元素的key,则可以利用(index-1)*30,这个地址开始的20个字节就是他的key,而获取value时,可以利用((index-1)*30)+10,+10意思是跳过10个字节的key,并从这个地址开始的20个字节是他所对应的mime类型。

剩下就是比对请求url中的后缀名和MediaArray中哪个位置的key相同,并返回他的value。

我们把这部分逻辑放入media.asm中。

struc Media_Item
    .key    resb   10
    .value  resb   20
endstruc

SECTION .data
    HTML_MEDIA: ISTRUC Media_Item
        AT Media_Item.key, db 'html'
        AT Media_Item.value, db 'text/html'
    IEND
    
    IMAGE_PNG_MEDIA: ISTRUC Media_Item
        AT Media_Item.key, db 'png'
        AT Media_Item.value, db 'image/png'
    IEND
    
    IMAGE_JPG_MEDIA: ISTRUC Media_Item
        AT Media_Item.key, db 'jpg'
        AT Media_Item.value, db 'image/jpg'
    IEND
    
    IMAGE_CSS_MEDIA: ISTRUC Media_Item
        AT Media_Item.key, db 'css'
        AT Media_Item.value, db 'text/css'
    IEND
    IMAGE_JS_MEDIA: ISTRUC Media_Item
        AT Media_Item.key, db 'js'
        AT Media_Item.value, db 'text/javascript'
    IEND    
    MediaArray dd HTML_MEDIA,0h,IMAGE_PNG_MEDIA,0h,IMAGE_JPG_MEDIA,IMAGE_CSS_MEDIA,IMAGE_JS_MEDIA,0h
    
    MediaArraySize dd 5
section .text

getMediaIndexBySuffix:
        call    getSuffix
        mov     ebx,0   
        mov     eax,0
nextMediaArray:
        mov     ecx,[MediaArraySize]
        mov     edx,[MediaArray] ;;获取数组
        
        push    eax

        
        call    getMediaKey      ;;;根据索引ebx获取当前数组媒体类型
        mov     esi,suffixBuffer  ;;请求的文件后缀
       
        call    equalsStr          ;;判断请求后缀和当前是否想等
        cmp     eax,1       ;;如果相等
        pop     eax
        je      _finishFind
        inc     eax         ;;数组中下一个
        push    eax       
        sub     eax ,ecx
        pop     eax
        jz      _failFind
        jmp     nextMediaArray
        ret
_failFind:
        mov     eax,0
        ret        
_finishFind:
        call   getMediaValue
        mov     eax,1
        ret
;;更具索引ebx获取媒体key
getMediaKey:
        push    eax
        push    ebx
        
        mov     edi,[MediaArray] ;;获取数组
        mov     ebx,30
        mul     ebx  ;;得数保存在eax中
        add     edi,eax
        pop     ebx
        pop     eax
        ret
;;更具索引ebx获取媒体类型
getMediaValue:
        push    ebx
        mov     edi,[MediaArray] ;;获取数组
        mov     ebx,30
        mul     ebx  ;;得数保存在eax中
        add     edi,eax
        add     edi,10
        pop     ebx
        ret
;;比较esi和edi中的字符串        
equalsStr:
        push    ebx
        push    ecx
        call    getStringLength ;;获取
        mov     ebx,eax
        mov     ecx,eax
        rep     cmpsb
        pop     ecx
        pop     ebx
        je      _ok
        mov     eax,0
        ret
_ok:
        mov     eax,1
        ret     
      
;;获取扩展名称,源位于esi中
getSuffix:
        call    getStringLength
        push    esi
        mov     ecx, eax    ;;保存长度
_findPoint:
        dec     eax
        cmp     eax,-1
        jz      _finish
        cmp     byte[esi+eax],46 ;;找到ascii 46  .
        jnz     _findPoint
        mov     edi,suffixBuffer
        add     esi,eax
        inc     esi
        sub     ecx,eax
        dec     ecx
        rep     movsb     
_finish:          
        pop     esi
        ret
;;获取字符长度,源位于esi中
getStringLength:
       push     esi
       mov      eax,0
       dec      esi
_loopStr:       
       inc      eax
       cmp      byte [esi+eax],0
       jnz      _loopStr
       pop esi
       dec eax
       ret

首先会通过getSuffix获取后缀名,放入suffixBuffer中,思路是先获取请求url的长度,然后从后面开始遍历,在遇到ascii为46的时候,记录下当前长度,并使用总长度-当前位置得出需要在请求url偏移多少字节开始保存后缀名。

有了后缀名就可以进入nextMediaArray查找这个后缀名位于MediaArray那个位置。

接下来就是拼接字符,还记得上一章的utils吗,我们在这里扩展,主要就是在设置完响应大小后,接着拼接Content-Type。

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

    mov     ecx,ebx
    mov     eax,0
_loop:
    nop
    pop     edx     ;;获取第n位
    add     edx,48   ;;加上48是ascii码
    mov     [headersBuffer+32+eax],edx ;;向buffer中增加长度
    inc     eax ;;偏移
    loop     _loop
  
    mov     edi,headersBuffer
    add     edi,32
    add     edi,eax ;;n长度的相应大小
    
    call   setResponseMedia   ;;设置相应头
    
    mov     esi,header_end  ;;4个字节的结尾
    mov     ecx,4
    rep     movsb
    
    ret
setResponseMedia:
    mov     esi,header_line
    mov     ecx,2
    rep     movsb
    

    mov     esi,response_header_content_type  ;;复制content-type到buffer中
    mov     ecx,13
    rep     movsb
    
    add     eax,15
    
    ;;到这里buffer中的数据如下,eax是len(285779)+2+13的大小
    ;;HTTP/1.1 200 OK
    ;;Content-Length:285779
    ;;content-type:
    ;;这个时候只有edi和eax是需要用的
    
    push    edi     ;;保存buffer
    push    eax
    
    mov     esi,fullPath    ;;参数
    call    getMediaIndexBySuffix   ;;获取这个请求的media类型,参数是esi,结果位于edi,如果eax是1
    cmp     eax,0       ;;如果没找到了这个媒体类型
    je      setDefaultMedia
copy:
    mov     edx,edi     ;;保存结果到edx中
    pop     eax     
    pop     edi
    call    copyMediaToResponse  ;;将这个媒体类型复制到buffer中

    ret
setDefaultMedia:
     mov    edi,default_media  
     jmp    copy
     ret    
copyMediaToResponse:
     push   eax
     mov    esi,edx          ;;源字符是由getMediaIndexBySuffix获得或者是default_media
     call   getStringLength ;;获取他的长度
     mov    ecx,eax
     mov    ebx,ecx
     rep    movsb
     pop    eax
     add    eax,ebx 
     ret