[linux-riscv] Fazendo um servidor HTTP
Na parte anterior aceitamos uma conexão TCP. Agora vamos transformar nosso programa em um servidor HTTP que um navegador consegue acessar!
Syscall read
Para receber a requisição HTTP do cliente, usamos a syscall read (63 no RISC-V):
- a0 = fd
- a1 = ponteiro do buffer
- a2 = tamanho máximo
- Retorno em a0 = bytes lidos
Criamos um buffer na seção .bss (dados não inicializados):
.section .bss
buffer:
.space 4096 # reserva 4096 bytes (equivalente a resb 4096 do NASM)
A resposta HTTP
A resposta HTTP é uma string com status line, headers e corpo HTML. Montamos tudo no .rodata:
.section .rodata
resposta_200:
.ascii "HTTP/1.1 200 OK\r\n"
.ascii "Content-Type: text/html\r\n"
.ascii "Connection: close\r\n"
.ascii "\r\n"
.ascii "<html>\r\n"
.ascii "<head><title>Baixaria RISC-V HTTP</title></head>\r\n"
.ascii "<body bgcolor=\"black\">\r\n"
.ascii "<center><h1 style=\"color: #00ff00\">Ola mundo da baixaria no RISC-V!</h1></center>\r\n"
.ascii "<hr>\r\n"
.ascii "<center style=\"color: #00ff00\">asm4noobs — servidor RISC-V em assembly puro!</center>\r\n"
.ascii "</body>\r\n"
.ascii "</html>\r\n"
resposta_200_fim:
No GNU assembler, as diretivas
.asciiconsecutivas são concatenadas na memória, formando uma única string contínua — o mesmo efeito de múltiplosdbno NASM.
Repare que removemos o Content-Length e usamos Connection: close. O navegador sabe que recebeu tudo quando a conexão fecha. Isso evita ter que calcular o tamanho do HTML manualmente.
Para enviar a resposta, calculamos o tamanho subtraindo labels:
la a1, resposta_200
la t0, resposta_200_fim
sub a2, t0, a1 # tamanho = resposta_200_fim - resposta_200
Isso evita usar li a2, resposta_200_len antes do assembler conhecer o valor do símbolo.
Roteamento: 200 ou 404
Precisamos verificar se a requisição começa com GET / . Como RISC-V não tem comparação direta de memória com constante, fazemos byte a byte:
# Verificar se buffer começa com "GET / "
lb t0, 0(a1) # buffer[0] == 'G'?
li t1, 'G'
bne t0, t1, .erro_404
lb t0, 1(a1) # buffer[1] == 'E'?
li t1, 'E'
bne t0, t1, .erro_404
lb t0, 2(a1) # buffer[2] == 'T'?
li t1, 'T'
bne t0, t1, .erro_404
lb t0, 4(a1) # buffer[4] == '/'?
li t1, '/'
bne t0, t1, .erro_404
lb t0, 5(a1) # buffer[5] == ' '?
li t1, ' '
bne t0, t1, .erro_404
# É um GET / válido!
O RISC-V não tem a “mágica” do NASM com
cmp dword [buffer], "GET ". A comparação é sempre registrador vs registrador (ou registrador vs imediato pequeno). Para strings, usamos load byte + comparação.
Código completo
.section .text
.global _start
enviar:
# a0=fd, a1=buffer, a2=tamanho
li a7, 64
ecall
ret
# Para manter o exemplo pequeno, fazemos apenas uma syscall write.
# Em um servidor robusto, seria preciso repetir caso write envie só parte do buffer.
tratar_cliente:
# a0 = fd do cliente
addi sp, sp, -16
sd ra, 8(sp)
sd s0, 0(sp)
mv s0, a0 # s0 = fd do cliente
# read(fd, buffer, 4096)
li a7, 63 # syscall read
mv a0, s0
la a1, buffer
li a2, 4096
ecall
# Ignoramos o retorno (bytes lidos) — só precisamos do conteúdo no buffer
# Verificar "GET / "
la a1, buffer
lb t0, 0(a1)
li t1, 'G'
bne t0, t1, .erro_404
lb t0, 1(a1)
li t1, 'E'
bne t0, t1, .erro_404
lb t0, 2(a1)
li t1, 'T'
bne t0, t1, .erro_404
lb t0, 4(a1)
li t1, '/'
bne t0, t1, .erro_404
lb t0, 5(a1)
li t1, ' '
bne t0, t1, .erro_404
.rota_ok:
mv a0, s0
la a1, resposta_200
la t0, resposta_200_fim
sub a2, t0, a1
call enviar
j .fim
.erro_404:
mv a0, s0
la a1, resposta_404
la t0, resposta_404_fim
sub a2, t0, a1
call enviar
.fim:
ld ra, 8(sp)
ld s0, 0(sp)
addi sp, sp, 16
ret
_start:
# 1. socket
li a7, 198
li a0, 2
li a1, 1
li a2, 0
ecall
mv s0, a0
# 2. bind
li a7, 200
mv a0, s0
la a1, sockaddr_in
li a2, 16
ecall
# 3. listen
li a7, 201
mv a0, s0
li a1, 10
ecall
# 4. accept
li a7, 202
mv a0, s0
li a1, 0
li a2, 0
ecall
mv s1, a0 # guarda fd do cliente
# 5. Tratar requisição
mv a0, s1 # argumento: fd do cliente
call tratar_cliente
# 6. Fechar cliente
li a7, 57
mv a0, s1
ecall
# 7. Fechar servidor
li a7, 57
mv a0, s0
ecall
# 8. exit
li a7, 93
li a0, 0
ecall
.section .data
sockaddr_in:
.hword 2
.hword 0x911F
.word 0
.dword 0
.section .bss
buffer:
.space 4096
.section .rodata
resposta_200:
.ascii "HTTP/1.1 200 OK\r\n"
.ascii "Content-Type: text/html\r\n"
.ascii "Connection: close\r\n"
.ascii "\r\n"
.ascii "<html>\r\n"
.ascii "<head><title>Baixaria RISC-V HTTP</title></head>\r\n"
.ascii "<body bgcolor=\"black\">\r\n"
.ascii "<center><h1 style=\"color: #00ff00\">Ola mundo da baixaria no RISC-V!</h1></center>\r\n"
.ascii "<hr>\r\n"
.ascii "<center style=\"color: #00ff00\">asm4noobs — servidor RISC-V em assembly puro!</center>\r\n"
.ascii "</body>\r\n"
.ascii "</html>\r\n"
resposta_200_fim:
resposta_404:
.ascii "HTTP/1.1 404 Not Found\r\n"
.ascii "Content-Type: text/html\r\n"
.ascii "Connection: close\r\n"
.ascii "\r\n"
.ascii "<html><head><title>404</title></head>\r\n"
.ascii "<body bgcolor='black'><center>\r\n"
.ascii "<h1 style='color: red'>404 — Nao achei a baixaria RISC-V!</h1>\r\n"
.ascii "</center></body></html>\r\n"
resposta_404_fim:
Testando
Compile e execute com QEMU:
make clean
make
qemu-riscv64 ./helloworld &
# Em outro terminal:
curl http://localhost:8081/
curl http://localhost:8081/invalido
Resposta 200:
<html>
<head><title>Baixaria RISC-V HTTP</title></head>
<body bgcolor="black">
<center><h1 style="color: #00ff00">Ola mundo da baixaria no RISC-V!</h1></center>
<hr>
<center style="color: #00ff00">asm4noobs — servidor RISC-V em assembly puro!</center>
</body>
</html>
Resposta 404:
<html><head><title>404</title></head>
<body bgcolor='black'><center>
<h1 style='color: red'>404 — Nao achei a baixaria RISC-V!</h1>
</center></body></html>
Salvando o progresso
git add helloworld.S
git commit -am "Servidor HTTP RISC-V funcionando com rotas 200 e 404!"
Conclusão da quarta parte
Temos um servidor HTTP funcional no RISC-V! Aprendemos:
- Syscall
read(63) para receber dados do cliente -
.spacepara declarar buffers não inicializados (.bss) -
.asciipara strings com\r\nembutidos - Comparação byte a byte para roteamento HTTP
-
Connection: closepara evitar calcularContent-Length
Na próxima e última parte: loop de conexões e toques finais!