Servidor web com bash

Postado 15:48 24/06/2022 por THIAGO CONDÉ COMPARTILHAR

voltar

Um servidor Web em Bash

2021-03-16

Este é um projeto meio idiota, tem todos os tipos de bugs e eles são do tipo horripilante. Os scripts de shell têm acesso direto ao computador e, portanto, você pode fazer todo tipo de coisas estranhas e prejudiciais. Mas tudo isso me faz sorrir que tudo funciona mesmo.

O código-fonte pode ser encontrado em:

https://github.com/Krowemoh/bash-server

Capítulo 1 - Olá, (para o estranho) mundo!

Vamos escrever o script bash mais simples apenas para nos orientar. Abra um arquivo de texto e adicione apenas a seguinte linha e salve-o.

./test.sh

 bash
echo "Olá, mundo!"

Agora podemos executar nosso script na linha de comando fazendo o seguinte.

 bash
> bash test.sh

Aqui estamos especificando o interpretador que queremos usar e o script que queremos alimentar no interpretador. Vamos usar o bash, mas a maioria dos shells funcionará da mesma maneira.

Depois de executarmos nosso comando, devemos obter "Hello, World!" impresso em nossa tela. Por padrão, o echo será impresso em stdout, que é a saída padrão.

./test.sh

 bash
echo "Olá, stdout!" >& 1 # stdout
echo "Olá, stderr!" >& 2 # stderr
echo "Olá, arquivo!" > temp.txt # arquivo

Aqui estamos redirecionando explicitamente a saída de echo para & 1, & 2 e um novo arquivo.

& 1 é o descritor de arquivo para saída padrão.

& 2 é o descritor de arquivo para erro padrão.

A falta de espaço após o > é importante quando estamos enviando a saída para descritores de arquivo.

 bash
> bash test.sh

Agora, quando executarmos este script, devemos obter 2 linhas de texto impressas na tela e 1 linha em um novo arquivo chamado temp.txt. Voilá! Temos um script que pode enviar saídas para diferentes saídas agora.

A seguir, vamos ver como executar nosso script sem especificar o interpretador na linha de comando. Queremos poder enviar este script para outra pessoa e executá-lo sem ter que saber qual interpretador de shell eles devem usar.

Capítulo 2 - Começando com um (#!sh)bang

Quando tentamos executar um programa no linux, o ambiente shell chamará exec no programa. Se for um binário, ele terá todas as informações necessárias para começar a funcionar para que possa ser carregado na memória e o sistema passará o controle para o programa. Os scripts são um pouco diferentes, eles precisam de um intérprete para ler o código-fonte e fazer coisas com base nele.

Isso significa que precisamos, de alguma forma, informar ao sistema qual intérprete certos scripts devem usar. É aqui que o símbolo de hash tag bang, #! entra. No topo de um arquivo podemos adicionar #! e isso informará ao sistema qual intérprete usar.

./test.sh

 bash
# !/usr/bin/bash
 
echo "Olá, mundo!"

Agora adicionamos um shbang ao nosso script e especificamos o caminho completo para o interpretador que queremos que o sistema use.

 > chmod +x teste.sh

Marcamos nosso script como executável para que possamos executá-lo diretamente.

 bash
> ./test.sh

Magicamente, nosso script agora é executado sem que usemos um interpretador explicitamente!

O #! é um número mágico, em hexadecimal é 0x23 0x21, e quando o script é chamado do shell e passado para o exec, o exec sabe que é um script de shell pedindo que um interpretador seja executado. Na realidade, esse shbang é um comentário em nosso programa bash. Ele existe como uma forma de nos comunicarmos diretamente com o sistema. Estamos dizendo para usar o programa especificado em nosso caminho, usando este arquivo como entrada.

./test.sh

 bash
# !/usr/bin/ls
 
echo "Olá, mundo!"

Aqui estamos dizendo use ls e dê a entrada do nosso arquivo. Nesse caso, isso simplesmente imprimirá nosso arquivo como se tivéssemos feito "ls test.xh" na linha de comando.

 bash
# !/usr/bin/bash
# !/usr/bin/ls
 
echo "Olá, mundo!"

A primeira linha informa corretamente ao sistema qual interpretador usar e, assim que terminar, nosso interpretador começará a analisar o arquivo. Agora, quando o intérprete vê as hashtags, ele considera todos os comentários e os ignora.

É também por isso que a primeira linha precisa ser o shbang, caso contrário não poderemos informar ao sistema qual interpretador usar.

! Pronto, podemos marcar nosso script como executável, usar o shbang para definir nosso interpretador e executar nosso script como um binário.

A seguir, vamos ver algo que facilitará a vida mais tarde!

Capítulo 3 - Executando com argumentos

Atualmente nosso pequeno script apenas imprime uma mensagem, mas seria bom se pudéssemos passar alguns argumentos.

 bash
# !/usr/bin/bash
 
echo "Olá, $1"

Podemos acessar argumentos fazendo $1, $2 e assim por diante, para o número de argumentos que queremos processar. Também podemos obter todos os argumentos passados ​​fazendo $@.

 bash
> ./test.sh Nivethan

Se executarmos nosso script passando um nome, agora poderemos vê-lo.

Antes de começarmos a trabalhar em nosso servidor, vamos adicionar mais um utilitário que facilitará nossa vida. Registrando!

Capítulo 4 - Eu quero ser um madeireiro quando crescer

Atualmente, estamos imprimindo coisas na saída padrão por padrão e podemos gravar no erro padrão redirecionando a saída para & 2. Podemos escrever uma abstração sobre isso para que possamos ter uma função de log que gravará na saída padrão automaticamente e também imprimirá essas mensagens apenas se estivermos executando com o nível de depuração correto.

./test.sh

 bash
# !/usr/bin/bash
declare -r DEBUG=1
 
registro() {
if [ $DEBUG = 1 ]
então
echo "$1" >& 2;
fi
}
 
log "Testando..."

Aqui temos uma função de log e podemos passar apenas 1 argumento. Isso ocorre porque usamos $ 1 diretamente. Poderíamos fazer $@ e imprimir todos os argumentos que passaram para a função de log.

A outra coisa a notar é que estamos usando a sintaxe de declaração para configurar uma variável global chamada DEBUG que é somente leitura. Nosso script não deve ser capaz de alterar isso enquanto estiver em execução.

Agora com a configuração fora do caminho, podemos dar uma olhada na abertura de um soquete e começar a construir o núcleo do nosso pequeno servidor http.

Capítulo 5 - A internet é feita de (net)gatos

Netcat é um utilitário linux que pode se ligar a um soquete e exibir o que ele recebe na tela. Você pode usar o netcat tanto no modo de escuta quanto no modo de conexão.

Usando netcat

 bash
nc --ouvir 0.0.0.0 7999

Podemos executar isso na linha de comando e devemos ver nc apenas travando. Ele está esperando que algo seja enviado para 0.0.0.0 na porta 7999.

0.0.0.0 é uma abreviação para dizer que queremos vincular a todas as interfaces nesta máquina. Por padrão, o netcat será vinculado a 127.0.0.1, que é a interface de loopback, mas só pode ser acessado de dentro do computador. Para torná-lo acessível do lado de fora, precisaríamos nos vincular às interfaces voltadas para a rede.

Podemos especificar o endereço IP real da máquina, como 192.168.1.101, mas usar 0.0.0.0 é simples e funcionará mesmo que o IP da máquina mude no futuro.

Também especificamos a porta que queremos que o netcat escute. Para disponibilizar a porta, precisamos atualizar nosso firewall.

/etc/firewalld/zones/public.xml

 bash
< porta protocol="tcp" porta="7999"/ >

Adicionamos o acima ao nosso firewall e só precisamos recarregar nosso firewall.

 bash
#  firewall-cmd --reload

Agora devemos abrir nosso navegador e navegar até o endereço IP da máquina com o número da porta.

 bash
❯ nc --ouvir 0.0.0.0 7999
GET/HTTP/1.1
Anfitrião: 192.168.1.101:7999
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0
Aceitar: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp, */* ;q=0.8
Aceitar-Idioma: en-US,en;q=0,5
Aceitar-codificação: gzip, deflate
Conexão: manter vivo
Cookie: compact_display_state=false; filtro=todos; session_id=1db271d4-26e8-439d-99b5-b7db1a377afc
Solicitações de atualização inseguras: 1

Voilá! Temos o netcat escutando e a requisição que o navegador fez foi impressa em nossa tela. Agora temos uma maneira de abrir um soquete e ouvi-lo, vamos começar a trabalhar em nosso script!

Adicionando netcat à mistura

O Netcat atualmente imprime o que recebe na tela, o que realmente queremos que ele faça é passar esses dados diretamente para nós para que possamos processar a solicitação e respondê-la. Para fazer isso, usaremos a opção --sh-exec que diz ao netcat para passar toda a entrada que recebe e executar um novo processo especificado pelo sinalizador.

./server.sh

 bash
# !/usr/bin/bash
declare -r DEBUG=1
 
registro() {
if [ $DEBUG = 1 ]
então
echo "$1" >& 2;
fi
}
 
echo "Olá, mundo!"

Agora que estamos começando a trabalhar de verdade, renomeei nosso script de teste para server.sh. A partir de agora começaremos a construir lentamente o servidor HTTP!

 bash
> nc --listen 0.0.0.0 7999 --sh-exec "./server.sh"

Agora devemos atualizar nosso navegador e devemos ver "Hello, World!" em nossa tela. O Netcat estava escutando e quando recebeu a entrada, passou para server.sh que por sua vez imprimiu "Hello, World!". O Netcat enviará o que for enviado para a saída padrão no programa executado. É por isso que não vemos nada na janela do terminal, mas vemos algo no navegador.

 bash
# !/usr/bin/bash
declare -r DEBUG=1
 
registro() {
if [ $DEBUG = 1 ]
então
echo "$1" >& 2;
fi
}
 
echo "Olá, mundo"
log "Olá, erro"

Se executarmos novamente nosso comando netcat e navegarmos até o navegador, desta vez veremos a saída no navegador e no terminal!

Atualmente, o netcat termina após a primeira resposta que enviamos. Isso ocorre porque o netcat não fica aberto por padrão.

 bash
> nc --listen --keep-open 0.0.0.0 7999 --sh-exec "./server.sh"

Adicionamos o sinalizador --keep-open e com isso manteremos o netcat aberto para conexões futuras.

Abstração em abundância!

Temos o coração do nosso servidor quase começando a bater. Vamos fazer uma limpeza para que possamos fazer com que nosso script seja funcional por si só. Atualmente precisamos executar manualmente o comando netcat e chamar nosso script server.sh.

Queremos que nosso script bash contenha o comando netcat para que possamos executar apenas server.sh e iniciar nosso servidor http.

Para fazer isso, vamos criar uma função de serviço em nosso script que abrirá um socket e listenn e uma função de processo que enviará uma mensagem de volta.

./server.sh

 bash
# !/usr/bin/bash
declare -r DEBUG=1
 
registro() {
if [ $DEBUG = 1 ]
então
echo "$1" >& 2;
fi
}
 
servir() {
nc --listen --keep-open 0.0.0.0 7999 --sh-exec "./server.sh process"
}
 
processo() {
echo "Olá, mundo"
log "Olá, erro"
}
 
"$1"

Nós escrevemos duas funções agora, temos nossa função serve que rodará o netcat e temos nossa função de processo que enviará uma mensagem. Também temos $ 1 no final do nosso script. Isso significa que, por padrão, se executarmos nosso script, nada acontecerá. Isso porque queremos poder acionar o netcat e processar tudo no mesmo arquivo.

Podemos dividir isso e provavelmente faz mais sentido assim, mas eu gosto de mantê-lo em um arquivo, pois isso me ajuda a manter tudo na minha cabeça.

 bash
> ./server.sh serve

Executamos nosso servidor e o chamamos passando o argumento serve. O $1 no final do nosso arquivo é substituído e faz uma chamada de função para servir.

Agora, quando navegamos para o navegador, o netcat obtém alguma entrada e se aciona, mas desta vez passa o processo como argumento. O processo será então chamado e voila! vamos imprimir o Hello World em nossa tela e terminal!

A seguir, vamos dar uma olhada na leitura das solicitações feitas pelo navegador e na resposta a elas!

Capítulo 6 - Aprendendo a ler

Atualmente, não fazemos nada com as solicitações que o navegador está nos enviando, então, como primeiro passo, vamos imprimi-las na tela.

A primeira coisa que precisamos fazer é começar a ler a entrada que é passada para nossa função de processo. Em vez de fazer a leitura diretamente em nossa função de processo, vamos criar uma nova função get_request_headers(). No entanto, antes de chegarmos lá, precisamos examinar como a entrada está sendo passada pelo netcat e pelo bash.

O Netcat enviará os dados brutos para a função de processo, mas uma vez na função de processo, esses dados serão divididos pelo delimitador que o bash usa. Atualmente, isso significa vários delimitadores. No bash, os dados podem ser divididos por meio de qualquer tipo de espaço em branco, o que significa que guias, espaços e novas linhas atuam como delimitadores.

Separador de campo interno

HTTP usa espaços novas linhas como delimitador para cada parte do cabeçalho e uma linha em branco para indicar o início do corpo ou o fim da solicitação. O HTTP também usa espaços no primeiro cabeçalho para definir o tipo de solicitação, local e versão.

Queremos que o script use um único delimitador, a nova linha, como delimitador para todos os dados.

 bash
# !/usr/bin/bash
declare -r CR=$' \r '
declare -r LF=$' \n '
declare -r CR_LF="$$"
 
declare -r DEBUG=1
 
...
processo() {
IFS=$LF
 
echo "Olá, mundo"
log "Olá, erro"
}

Vamos declarar mais algumas variáveis ​​globais somente leitura e, em seguida, em nossa função de processo, definimos uma variável especial chamada IFS para o caractere de alimentação de linha. Isso define o separador de campo interno para a nova linha. Isso significa que o bash agora analisará a entrada ao longo de novos caracteres de linha em vez de usar espaços.

Isso ficará mais óbvio quando analisarmos como lemos os dados.

Leitura 101

A primeira etapa é chamar nossa função de solicitação em nossa função de processo.

 bash
...
processo() {
IFS=$' \n '
 
get_request_headers
 
echo "Olá, mundo"
log "Olá, erro"
}
...

Agora que estamos chamando nossa função para obter cabeçalhos de solicitação, agora podemos lidar com todas as leituras lá.

 bash
...
get_request_headers() {
request_headers=()
 
enquanto verdadeiro
Faz
leia o cabeçalho
log "$header"
if [ "$cabeçalho" = $CR_LF]
então
parar
fi
request_headers=("${request_headers[@]}" "$header")
feito
}
...

A primeira coisa que fazemos em nossa função é inicializar um array vazio. Em seguida, iniciamos um loop infinito que terminará quando chegarmos a uma linha em branco. Uma linha em branco significa o fim da solicitação ou pode significar o início do corpo. Por enquanto, vamos usar uma linha em branco para significar o fim da solicitação.

read é uma função bash que lê em uma unidade da entrada padrão. Nesse caso, isso significa que ele lerá uma linha inteira de uma solicitação http que o netcat recebeu. É por isso que precisamos mudar o IFS. O HTTP mistura espaços e novas linhas, por isso queremos garantir que processamos uma linha inteira antes de passar para a próxima.

Depois de ler o cabeçalho, concatenamos o array request_headers existente com o novo elemento que acabamos de ler. Quando o loop while terminar, teremos um array de cabeçalhos http em uma variável chamada request_headers.

Vamos tentar navegar para o nosso servidor através do navegador.

 bash
n
❯ ./test.sh serve
GET/HTTP/1.1
Anfitrião: 192.168.1.101:7999
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0
Aceitar: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp, */* ;q=0.8
Aceitar-Idioma: en-US,en;q=0,5
Aceitar-codificação: gzip, deflate
Conexão: manter vivo
Cookie: compact_display_state=false; filtro=todos; session_id=1db271d4-26e8-439d-99b5-b7db1a377afc
Solicitações de atualização inseguras: 1
Controle de cache: max-age=0
 
Olá, erro
GET /favicon.ico HTTP/1.1
Anfitrião: 192.168.1.101:7999
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0
Aceitar: imagem/webp, */*
Aceitar-Idioma: en-US,en;q=0,5
Aceitar-codificação: gzip, deflate
Conexão: manter vivo
Referenciador: http://192.168.7.41:7999/
Cookie: compact_display_state=false; filtro=todos; session_id=1db271d4-26e8-439d-99b5-b7db1a377afc
Controle de cache: max-age=0
 
Olá, erro

Voilá! Temos 2 solicitações do navegador registradas. Temos uma solicitação para / que é a rota padrão. Também temos um pedido para /favicon.ico.

Se não definirmos o IFS, veremos GET, /favicon.ico, HTTP/1.1 e o restante das seções em linhas separadas.

Agora que nosso pequeno servidor pode ver nossas solicitações e as processou, podemos começar a tratá-las. Se pudermos devolver as coisas solicitadas, teríamos um servidor web muito rudimentar!

Capítulo 7 - Vamos ao que interessa (lidar) com os negócios

Assim como lemos nos cabeçalhos, adicionaremos outra função para lidar com solicitações em nossa função de processo.

 bash
...
processo() {
IFS=$LF
 
get_request_headers
 
handle_requested_resource
 
echo "Olá, mundo"
log "Olá, erro"
}
...

Vamos lidar com uma solicitação verificando se o local solicitado é um arquivo válido e, se for, queremos retornar os dados contidos nesse arquivo para o navegador.

./app/index.html

 bash
< !DOCTYPE html >
< html lang="pt" >
 < cabeça >
 < meta charset="utf-8" >
 < título > Diversão! < /título >
 < /cabeça >
 < corpo >
 < h1 > Olá Mundo! < /h1 >
 < /corpo >
< /html >

Aqui temos um index.html muito simples em uma pasta chamada app.

 bash
handle_requested_resource() {
regexp=".* (.*) HTTP"
[[ "${request_headers[0]}" =~ $regexp ]]
 
resource="${BASH_REMATCH[1]}"
 
request_resource="./app$resource"
if [ -f "$requested_resource" ]
então
cat "$requested_resource"
fi
}

A primeira coisa que precisamos fazer é obter o local que a solicitação está especificando. Podemos usar regex e especificamos um grupo de captura. O grupo de captura é sinalizado pelo (). Podemos então executar essa regex em algum texto ou variável colocando-a entre colchetes duplos e usando =~. Esta é uma sintaxe estranha. Colchetes no bash significam testar e, portanto, isso provavelmente está testando o regex em relação à variável, mas não é fácil saber apenas olhando para ele.

As capturas são então armazenadas em uma variável bash especial chamada BASH_REMATCH. Esta é uma matriz de correspondências e no nosso caso será apenas uma matriz de 1. Agora temos o recurso que está sendo solicitado. Em seguida, anexamos ao ./app, pois queremos servir apenas os arquivos localizados em app.

Em seguida, verificamos se o recurso solicitado é um arquivo. Se for um arquivo vamos cat o arquivo. O gato envia o conteúdo do arquivo para a saída padrão que neste caso passa pelo netcat e retorna ao navegador.

Voilá! Devemos ser capazes de navegar para o navegador e navegar para 192.168.1.101:7999/index.html e ver nosso arquivo html simples. O único problema é que ele será renderizado como texto bruto.

Isso ocorre porque, quando enviamos a saída de volta ao navegador, não enviamos cabeçalhos e, portanto, o navegador não sabe o que fazer com a resposta e o padrão é texto bruto.

No próximo capítulo, adicionaremos os cabeçalhos de resposta e configuraremos o tipo MIME e os comprimentos de conteúdo adequados!

Capítulo 8 - Para derrotar os hunos

Atualmente, podemos lidar com solicitações e enviar de volta os arquivos corretos, mas como não estamos definindo nenhum cabeçalho, nossas respostas estão sendo exibidas como texto bruto. Antes de resolvermos isso, vamos tornar nossa página index.html um pouco mais complexa.

./app/index.html

 html
< !DOCTYPE html >
< html lang="pt" >
 < cabeça >
 < meta charset="utf-8" >
 < link rel="Ícone de atalho" type="image/x-icon" href="favicon.ico" >
 < título > Diversão! < /título >
 < link rel="stylesheet" href="/css/style.css" >
 < script src="/css/script.js" >< /script >
 < /cabeça >
 < corpo >
 < h1 > Olá Mundo! < /h1 >
 
 < hora >
 < h2 > Teste PNG < /h2 >
 < img src="/images/test.png" >
 
 < hora >
 < h2 > Teste JPG < /h2 >
 < img src="/images/test.jpg" >
 
 < hora >
 < h2 > Teste MP4 < /h2 >
 < largura do vídeo="200" altura="200" controles >
 < source src="/videos/test.mp4#t=0.01" type="video/mp4" >
 < /vídeo >
 < /corpo >
< /html >

Agora temos uma página da Web relativamente completa com vários pedidos. Temos pedidos de favicon, style.css, script.js, test.png, test.jpg e test.mp4. Todos os arquivos também estão localizados em seus próprios arquivos específicos de recursos que, por sua vez, estão todos no diretório do aplicativo.

Agora podemos ver os problemas que vamos enfrentar. Temos todos os tipos de formatos e extensões para lidar, temos dados de imagem e dados de texto, também temos um vídeo para enviar. Se nosso servidor pudesse lidar com todos esses tipos, seria um pequeno servidor capbale.

Vamos começar!

A primeira coisa que faremos é em vez de chamar a função cat in out handle, vamos criar uma nova função chamada send_file.

 bash
...
handle_requested_resource() {
 
regexp=".* (.*) HTTP"
[[ "${request_headers[0]}" =~ $regexp ]]
 
resource="${BASH_REMATCH[1]}"
 
request_resource="./app$resource"
if [ -f "$requested_resource" ]
então
send_file "$requested_resource"
fi
}
...

Vamos passar o recurso solicitado para nossa função e esta função send_file assumirá a responsabilidade de configurar os cabeçalhos e o comprimento do conteúdo.

 bash
...
Enviar arquivo() {
# - >tipo de conteúdo
request_resource="$1"
extension="${requested_resource##*.}"
set_response_content_type "$extension"
 
# - >dados | Comprimento do conteúdo
get_requested_content "$1"
 
# - >cabeçalhos de resposta
set_response_headers "$content_type" "$content_length"
 
# - > resposta
build_response "$response_headers" "$conteúdo"
 
# - >Dados ECHO | IMPRIMIR dados
send_response "$resposta"
}
...

Primeiro, obtemos a variável que foi passada, o bash não passa variáveis ​​nomeadas, cada função pode acessar os argumentos por meio de $1, $2 e assim por diante, dependendo de quantos argumentos são passados.

Recebemos a extensão do arquivo usando a expansão de parâmetros. Aqui os meios que estamos procurando para corresponder a algo. A opção * significará remover. O comando de extensão, portanto, significa combinar para incluir o primeiro ponto que vemos e remover esses caracteres. A extensão seria o resultado dessa expansão.

Isso parece uma forma estranha de regex e pode ser mais simples ou mais fácil de entender se usarmos regex.

 bash
...
regex=". * \. (.* )"
[[ "$requested_resource" =~ $regex]]
extension="${BASH_REMATCH[1]}"
...

A expansão do parâmetro é mais concisa, então vamos usá-la por enquanto.

Definindo o cabeçalho do tipo de conteúdo

Agora que temos a extensão, podemos definir o Content-Type na resposta que queremos enviar. Arquivos HTML devem ser enviados de volta como text/html e png deve ser image/png.

 bash
...
set_response_content_type() {
caso "$1" em
"html")
content_type="Tipo de conteúdo: texto/html"
;;
"cs")
content_type="Tipo de conteúdo: texto/css"
;;
"js")
content_type="Tipo de conteúdo: texto/javascript"
;;
"ico")
content_type="Tipo de conteúdo: imagem/x-icon"
;;
"png")
content_type="Tipo de conteúdo: imagem/png"
;;
"jpg" | "jpeg")
content_type="Tipo de conteúdo: imagem/jpeg"
;;
"mp4")
content_type="Tipo de conteúdo: video/mp4"
;;
*)
content_type="Tipo de conteúdo: texto/simples"
;;
esac
}
...

Passamos a extensão e com base na extensão vamos definir um content_type. Estamos usando uma instrução case e o bash usa um case de aparência muito estranha. Tudo está desequilibrado, o que é legal.

Obtendo o conteúdo e definindo o cabeçalho de comprimento do conteúdo

 bash
...
get_requested_content() {
comprimento=$(stat --printf "%s" "$1")
content_length="Conteúdo-Comprimento: $comprimento"
content=$(cat "$1" | sed 's/ \\ / \\\\ /g' | sed 's/%/%%/g' | sed 's/ \x 00/ \\ x00/g' )
}
...

Passamos o nosso request_resource e iremos cat o arquivo e escapar de certas coisas.

Nós usamos o read the length antes de escaparmos os dados, pois o processo de escape é apenas para que possamos usar printf mais tarde. Não queremos contar acidentalmente os caracteres de escape como parte do nosso Content-Length. Podemos usar o comando stat para obter o tamanho do arquivo e com isso estamos prontos.

Para escapar de nossos dados, usamos sed e substituímos alguns caracteres problemáticos. Estaremos usando printf mais tarde e, portanto, precisaremos escapar de barras e porcentagens, pois printf usa esses caracteres. Também queremos usar sed para substituir nulos por um nulo com escape. Dessa forma, podemos enviar os dados usando printf. Sed funciona indo linha por linha fazendo uma substituição.

Para imagens e vídeos, precisaremos usar printf para enviar os dados, devido aos caracteres usados ​​neles. Arquivos de texto como html e CSS podemos usar o eco direto.

Uma vez que temos nossos dados, calculamos o Content-Length usando o bash embutido. Isso nos dará a duração do conteúdo que vamos veicular.

Agora temos nosso Content-Type e Content-Length e nosso Content. Agora podemos definir o restante de nossos cabeçalhos de resposta.

Definindo a versão, a data e os cabeçalhos de conexão

 bash
...
set_response_headers() {
version="HTTP/1.1 200 OK"
data="Data: $(data)"
connection="Conexão: Fechada"
 
response_headers="$$CR_LF$$CR_LF$$CR_LF$1$CR_LF$2$CR_LF$"
}
...

Criamos a linha de status http, criamos o cabeçalho Date e o cabeçalho de conexão.

Em seguida, mesclamos todos os cabeçalhos, incluindo os cabeçalhos que passamos, que eram Content-Type e Content-Length.

Adicionando os cabeçalhos e o conteúdo para formar uma resposta

Agora temos todos os nossos cabeçalhos definidos, só precisamos adicionar os cabeçalhos e o conteúdo juntos.

 bash
...
build_resposta() {
resposta="$1$CR_LF$CR_LF$2"
}
...

Voilá! Temos nossa resposta, temos os cabeçalhos seguidos por 2 novas linhas e depois temos o corpo que é o conteúdo.

Enviando a resposta

 bash
...
enviar_resposta() {
printf -- "$1$CR_LF"
saída
}
...

Aqui, usamos printf sem formatação para enviar os dados e preenchemos a resposta com um caractere de nova linha final, pois estava tendo um comportamento estranho quando o tamanho exato do conteúdo estava sendo enviado de volta e o corpo continha apenas o arquivo. Acho que há algo que não entendo com a maneira como printf e escape estão trabalhando com a forma como os dados estão sendo enviados e o que o navegador espera.

Padding parece ter resolvido o problema e unificado a diferença lógica que eu tinha entre arquivos normais e imagens.

Assim que enviarmos os dados de volta, sairemos do script e voltaremos a sentar e ouvir outra conexão.

Agora devemos ser capazes de atualizar nossa página index.html e ver uma página completa com todos os nossos html carregados, nossas imagens carregadas, nosso css carregado e nosso javascript carregado.

! Agora montamos um servidor web muito básico!

Agora que temos arquivos sendo solicitados e tratados corretamente, vamos ver como enviar solicitações POST para nosso servidor web.

Capítulo 9 - Postal

Atualmente, podemos lidar com solicitações GET, mas não temos como lidar com solicitações POST. Vamos corrigir isso.

Vamos primeiro escrever uma página de login básica.

 bash
...
< !DOCTYPE html >
< html lang="pt" >
 < cabeça >
 < meta charset="utf-8" >
 < link rel="Ícone de atalho" type="image/x-icon" href="favicon.ico" >
 < título > Clube de Leitores de Mangá - Login! < /título >
 < link rel="stylesheet" href="/css/style.css" >
 < /cabeça >
 < corpo >
 < h1 > Clube de Leitores de Mangá - Login! < /h1 >
 
 < hora >
 
 < form action="/login" method="POST" >
 < etiqueta > Nome de usuário < /rótulo >
 < input type="texto" name="username" >
 
 < br >
 < etiqueta > Senha < /rótulo >
 < input type="senha" nome="senha" >
 
 < br >
 < input type="enviar" value="Enviar" >
 < /formulário >
 < /corpo >
< /html >
...

Aqui temos um formulário que recebe um nome de usuário e senha e os envia como uma solicitação POST para /login.

Agora precisamos analisar o corpo de uma solicitação se for uma solicitação POST.

 bash
...
processo() {
IFS=$LF
 
get_request_headers
 
get_request_body
 
handle_requested_resource
}
...

Agora adicionamos uma etapa em que, após obtermos os cabeçalhos, obteremos o corpo.

 bash
...
get_request_body() {
request_type="$(echo "${request_headers[0]}" | cut -d" " -f1)"
 
post_length=0
para i em "${request_headers[@]}"
Faz
header=$(cut -d":" -f1 <<< "$i")
if [ "$header" = "Comprimento do conteúdo" ]
então
post_length=$(echo "$i" | cut -d":" -f2 | tr -d "$CR" | tr -d "$LF" | tr -d ' ')
fi
feito
 
if [ "$post_length" -ne 0]
então
IFS= read -n "$post_length" request_body
fi
}
...

Quando esta função é chamada, temos um array de cabeçalhos e assim podemos verificar se o primeiro cabeçalho é uma requisição post. Aqui estamos usando a opção de corte para analisar o tipo da solicitação, mas também podemos usar regex aqui.

Em seguida, precisamos percorrer os cabeçalhos para encontrar o cabeçalho Content-Length. Assim como nossa resposta, as solicitações POST terão um comprimento de conteúdo que podemos usar para descobrir quantos dados estão no corpo.

Percorremos a procura do cabeçalho Content-Length e, quando o encontramos, analisamos o comprimento. Aqui fazemos alguns cortes e recortes onde removemos todos os caracteres de nova linha.

Uma vez que temos o comprimento, lemos mais dados da entrada padrão. Isso ocorre porque processamos tudo na entrada padrão até encontrar uma nova linha em branco. Uma vez que atingimos esta linha, voltamos ao nosso script e, portanto, se houver um corpo, ele permanecerá na entrada padrão.

Definimos o IFS aqui como nada, pois o corpo de uma resposta http pode conter novas linhas e queremos processar tudo no corpo. Também usamos o sinalizador -n para especificar quantos bytes queremos obter da entrada padrão.

 bash
handle_requested_resource() {
 
regexp=".* (.*) HTTP"
[[ "${request_headers[0]}" =~ $regexp ]]
 
resource="${BASH_REMATCH[1]}"
 
request_resource="./app$resource"
if [ -f "$requested_resource" ]
então
send_file "$requested_resource"
fi
 
log "$request_body"
}

Aqui, adicionamos uma linha após nossa instrução if, onde exibiremos o corpo da solicitação conforme foi analisado.

Voilá! Nós conectamos as solicitações POST, agora vamos configurar os pontos finais da rota para que possamos fazer algo útil com nossa solicitação POST.

Capítulo 10 - Quem não precisa de um dicionário

Agora que temos solicitações de postagem, vamos configurar as rotas. Dessa forma, podemos fazer solicitações para /login em vez de /login.html. Isso já não parece melhor!

O dicionário de funções é realmente um dispatcher. Ele analisa a url e, com base na url, envia para a função correta.

 bash
...
declare -r DEBUG=1
 
declare -A function_dictionary=(
[login]=login
)
...

A primeira coisa que vamos fazer é configurar um hashmap de dicionário de funções no bash. Felizmente, o armazenamento de funções é fácil no bash, pois as funções não recebem argumentos e podemos chamar a função apenas usando seu nome.

Aqui estamos dizendo que se obtivermos uma rota de login, chamemos login.

Em nosso processamento de solicitações, atualmente verificamos se o recurso solicitado existe. Agora, se o recurso não existir, verificaremos o dicionário de funções.

 bash
...
handle_requested_resource() {
regexp=".* (.*) HTTP"
[[ "${request_headers[0]}" =~ $regexp ]]
 
resource="${BASH_REMATCH[1]}"
 
request_resource="./app$resource"
if [ -f "$requested_resource" ]
então
send_file "$requested_resource"
fi
 
request_resource="${resource:1}"
 
para x em "${!function_dictionary[@]}"
Faz
if [[ "$requested_resource" =~ $x]]
então
${function_dictionary[$x]}
fi
feito
 
send_file "./app/404.html"
}
...

Aqui vamos percorrer o dicionário de funções e verificar se temos alguma correspondência no dicionário para a rota que estamos tentando acessar. Usamos o operador =~ para fazer uma correspondência de regex. Precisaremos fazer isso para que possamos lidar com rotas dinâmicas mais tarde.

Quando fazemos uma solicitação, fazemos isso indo para /login, então precisamos cortar o primeiro caractere, que é o que fazemos com a opção de dois pontos.

Este é um mapa de hash, então não precisamos usar um loop para verificar as chaves, mas precisaremos do loop mais tarde quando quisermos lidar com rotas variáveis ​​como /user/1 e /user/2.

p>

Agora vamos escrever nossa função de login!

 bash
...
Conecte-se() {
if [ "$request_type" = "GET" ]
então
send_file "./app/login.html"
 
senão
username=$(echo -n "$request_body" | cut -d' & ' -f1 | cut -d'=' -f2)
password=$(echo -n "$request_body" | cut -d' & ' -f2 | cut -d'=' -f2)
 
if [ "$senha" = "123" ]
então
session_id=$(uuidgen)
toque em "./sessions/$session_id"
cookies="Set-cookie: session_id=$session_id"
fi
send_file "./app/account.html"
fi
}
...

Agora podemos verificar se nossa solicitação é GET ou POST. O GET é simples, simplesmente queremos retornar a página login.html que já escrevemos.

No entanto, se a solicitação for uma postagem, podemos analisar as informações de login e fazer o login da pessoa!

Precisamos criar uma pasta de sessões ao lado de nosso aplicativo e, assim que um usuário estiver logado, criamos um token de sessão para ele. Também estamos definindo um cookie, mas ele ainda não está sendo usado.

 html
< !DOCTYPE html >
< html lang="pt" >
 < cabeça >
 < meta charset="utf-8" >
 < link rel="Ícone de atalho" type="image/x-icon" href="favicon.ico" >
 < título > Clube de Leitores de Mangá - Conta! < /título >
 < link rel="stylesheet" href="/css/style.css" >
 < /cabeça >
 < corpo >
 < h1 > Olá < /h1 >
 < hora >
 < div >< a href="/comics" > Quadrinhos < /a >< /div >
 < /corpo >
< /html >

Devemos ser capazes de navegar para /login e tentar fazer login com qualquer nome de usuário e com a senha 123 e devemos imprimir a página account.html!

Com isso temos nossos pontos finais começando a funcionar! Agora, no próximo capítulo, vamos conectar o modelo. Dessa forma, podemos ter comandos bash em nossos arquivos html e fazer todo tipo de coisas legais!

Capítulo 11 - Bashruptcy

Na página da nossa conta, vamos adicionar uma exibição simples de nome de usuário para que possamos ver com quem efetuamos login.

./app/account.html

 bash
< !DOCTYPE html >
< html lang="pt" >
 < cabeça >
 < meta charset="utf-8" >
 < link rel="Ícone de atalho" type="image/x-icon" href="favicon.ico" >
 < título > Clube de Leitores de Mangá - Conta! < /título >
 < link rel="stylesheet" href="/css/style.css" >
 < /cabeça >
 < corpo >
 < h1 > Olá, "$username" < /h1 >
 < hora >
 < div >< a href="/comics" > Quadrinhos < /a >< /div >
 < /corpo >
< /html >

Aqui temos uma variável bash bruta embutida em nosso html. Vamos usar o bash para considerar isso como uma string bash regular e, em seguida, executará todo o código bash dentro dela.

Dessa forma, não precisamos criar nossa própria linguagem de modelagem! Podemos simplesmente reutilizar o bash!

Nossa estratégia será solicitar uma página que seja um modelo. Portanto, precisamos obter o conteúdo desse arquivo, executar todo o código nele e gerar algum html e, em seguida, enviar esse html de volta.

 bash
...
render_template() {
template=$(eval "gato << - END
$(cat "$1")
FIM
")
}
...

O primeiro passo é nossa função render_template. Aqui vamos chamar eval contra o conteúdo do arquivo que estamos tentando usar como modelo. Queremos lidar com a entrada de várias linhas e, portanto, usamos o redirecionamento onde especificamos explicitamente o marcador de final de arquivo.

 bash
...
send_html() {
conteúdo="$1"
content_length="${#content}"
 
set_response_content_type "html"
set_response_headers "$content_type" "$content_length"
build_response "$response_headers" "$conteúdo"
 
send_response "$resposta"
}
...

O segundo passo é escrever um send_html. Temos uma função send_file mas que envia um arquivo sem transformações, ao invés disso queremos enviar um html direto que geramos e então podemos usar esta função para isso.

 bash
...
Conecte-se() {
if [ "$request_type" = "GET" ]
então
content="$(cat ./app/login.html)"
send_html "$conteúdo"
 
senão
username=$(echo -n "$request_body" | cut -d' & ' -f1 | cut -d'=' -f2)
password=$(echo -n "$request_body" | cut -d' & ' -f2 | cut -d'=' -f2)
 
if [ "$senha" = "123" ]
então
session_id=$(uuidgen)
toque em "./sessions/$session_id"
cookies="Set-cookie: session_id=$session_id"
fi
render_template "./app/account.html"
send_html "$modelo"
fi
}
...

Agora podemos substituir nossos comandos send_file por uma chamada para render_template e send_html.

Voilá! Agora devemos ser capazes de ir para o navegador e fazer login novamente e desta vez ser saudados com "Olá, nome de usuário"!

A avaliação é assustadora, pois podemos fazer todo tipo de coisa dentro do nosso html. Muito legal.

Agora que temos o dicionário de funções e os modelos prontos, temos as principais partes de um servidor web funcionando. A última peça importante em que trabalharemos é o gerenciamento de sessões. Atualmente, configuramos a sessão, mas não fazemos nada com ela. Vamos corrigir isso!

Capítulo 12 - Lembrando quem somos

Atualmente, quando um usuário faz login, criamos um token de sessão e usamos o sistema de arquivos para armazenar a sessão. Essa é uma maneira rápida e suja de fazer tudo funcionar.

 bash
...
Conecte-se() {
if [ "$request_type" = "GET" ]
então
content="$(cat ./app/login.html)"
send_html "$conteúdo"
 
senão
username=$(echo -n "$request_body" | cut -d' & ' -f1 | cut -d'=' -f2)
password=$(echo -n "$request_body" | cut -d' & ' -f2 | cut -d'=' -f2)
 
if [ "$senha" = "123" ]
então
session_id=$(uuidgen)
toque em "./sessions/$session_id"
cookies="Set-cookie: session_id=$session_id"
fi
render_template "./app/account.html"
send_html "$modelo"
fi
}
...

Isso é apenas para repassar o que fizemos. Agora precisamos conectar os cookies às nossas respostas para que sejam enviados de volta ao navegador. Depois disso, também precisamos analisar o cookie especificamente quando lemos os cabeçalhos da solicitação.

 bash
...
set_response_headers() {
version="HTTP/1.1 200 OK"
data="Data: $(data)"
connection="Conexão: Fechada"
 
if [ "$cookies" = "" ]
então
response_headers="$$CR_LF$$CR_LF$1$CR_LF$2$CR_LF$"
senão
response_headers="$$CR_LF$$CR_LF$$CR_LF$1$CR_LF$2$CR_LF$"
fi
}
...

A primeira coisa que fazemos é definir o cabeçalho do cookie se houver cookies para enviar de volta.

Agora que estamos definindo o cabeçalho do cookie na resposta, podemos analisar os cabeçalhos de solicitação para o token de sessão.

Esta função deve ser mesclada com o get_request_headers, pois a lógica é duplicada. Nós realmente devemos analisar enquanto lemos. Caí na armadilha da decomposição temporal, onde, como ler e analisar são duas etapas para mim, eu as separei. Nesse caso, faria sentido mantê-los juntos, pois a leitura dos cabeçalhos realmente exige analisá-los, pois há cabeçalhos que precisamos para obter o corpo. Isso também resultaria em parâmetros de consulta de solicitações get e parâmetros de solicitações post sendo tratados na mesma função e poderiam ser colocados na mesma variável no futuro.

 bash
...
get_request_body_cookies() {
request_type="$(echo "${request_headers[0]}" | cut -d" " -f1)"
 
post_length=0
para i em "${request_headers[@]}"
Faz
header=$(cut -d":" -f1 <<< "$i")
if [ "$header" = "Comprimento do conteúdo" ]
então
post_length=$(echo "$i" | cut -d":" -f2 | tr -d "$CR" | tr -d "$LF" | tr -d ' ')
 
elif [ "$header" = "Cookie" ]
então
regex=". *session_id=(.* );?"
[[ "$i" =~ $regex]]
session_id=$(echo "${BASH_REMATCH[1]}" | tr -d "$CR" | tr -d "$LF")
fi
feito
 
if [ "$post_length" -ne 0]
então
IFS= read -n "$post_length" request_body
fi
}
...

Aqui estamos renomeando nosso get_request_body para get_request_body_cookies, pois esta função também irá lidar com nossos cookies agora. Originalmente, usamos essa função para obter o Content-Length, então faz sentido usar essa função também para nossos cookies.

Agora verificamos se recebemos um cabeçalho Cookie e, se obtivermos, podemos usar regex e grupos de captura para obter o session_id do cookie que a solicitação enviou de volta.

Agora que temos o id, podemos criar uma função que verificará uma sessão.

 bash
...
check_session() {
E se [ ! -f "./sessions/$session_id" ]
então
render_template "./app/login.html"
send_html "$modelo"
fi
}
...

Esta é uma função auxiliar que podemos chamar no início de uma rota quando queremos ter certeza de que um usuário está logado antes de acessar a função. Se eles não estiverem logados, pediremos que eles façam login.

Antes de testarmos nosso tratamento de sessão, vamos adicionar um link de logout e configurar a rota para sair corretamente.

 bash
...
declare -A function_dictionary=(
[login]=login
[saída]=saída
[conta]=conta
)
...

Aqui, adicionamos a função de saída e conta ao nosso dicionário de funções para que possamos fazer a correspondência.

 bash
...
sair() {
if [ -f "./sessions/$session_id" ]
então
rm "./sessions/$session_id"
session_id=""
cookies="Set-cookie: session_id=$session_id"
fi
render_template "./app/login.html"
send_html "$modelo"
}
conta() {
check_session
render_template "./app/account.html"
send_html "$modelo"
}
...

A função de logout é bastante direta, simplesmente verificamos se a sessão existe na pasta da sessão e, se existir, excluímos a sessão, redefinimos o cookie e redirecionamos de volta para a página de login.

Nossa função de conta chama check_session antes de retornar a página da conta.

Agora só precisamos adicionar o link de saída à página da nossa conta.

 bash
...
< !DOCTYPE html >
< html lang="pt" >
 < cabeça >
 < meta charset="utf-8" >
 < link rel="Ícone de atalho" type="image/x-icon" href="favicon.ico" >
 < título > Clube de Leitores de Mangá - Conta! < /título >
 < link rel="stylesheet" href="/css/style.css" >
 < /cabeça >
 < corpo >
 < h1 > Olá, "$username" < /h1 >
 < hora >
 < div >< a href="/signout" > saída < /a >< /div >
 < div >< a href="/comics" > Quadrinhos < /a >< /div >
 < /corpo >
< /html >
...

Voilá! Agora devemos ser capazes de navegar para /account e ser mostrada a tela de login. Devemos ser capazes de fazer login e ver a página da nossa conta corretamente e, finalmente, devemos ser capazes de clicar em sair e ser desconectado. Em seguida, podemos navegar para /account novamente para verificar se realmente não conseguimos.

Com isso, temos uma parte importante do nosso servidor web funcionando!

Capítulo 13 - Aqui estamos

Agora que a lógica principal do nosso servidor está funcionando, vamos escrever um pequeno site de quadrinhos simples que listará todos os quadrinhos disponíveis nos quais podemos clicar para obter uma lista de capítulos que, se clicarmos mais uma vez, podemos ler esse capítulo . Para isso, usaremos mais lógica de modelagem e também adicionaremos regras de reescrita para que possamos abreviar como /comic/comic_name/1 para significar o capítulo 1 de comic_name.

A primeira coisa que faremos é configurar uma pasta em nosso diretório de aplicativos chamada comics. Dentro dos quadrinhos temos várias séries que queremos disponibilizar. Dentro da pasta da série teremos pastas para cada capítulo. Dentro de cada capítulo há um conjunto de imagens que é a história em quadrinhos. Vamos assumir que tudo está nomeado corretamente, de modo que a classificação padrão nos dê a ordem que queremos para todas as listagens.

Poderíamos tornar isso mais robusto usando um banco de dados, mas este é apenas um aplicativo simples para mostrar que nosso servidor bash é útil!

A página de quadrinhos!

Atualmente, nossa página de conta está vinculada a /comics, mas não leva a lugar nenhum, vamos corrigir isso.

 bash
declare -A function_dictionary=(
[login]=login
[saída]=saída
["quadrinhos"]=quadrinhos
)

Adicionamos quadrinhos ao nosso dicionário de funções para que possamos chegar ao ponto final dos quadrinhos.

 html
histórias em quadrinhos() {
check_session
render_template "./app/comics.html"
send_html "$modelo"
}

O ponto final dos quadrinhos renderizará o modelo de quadrinhos após verificar a sessão. A verificação de sessão não é realmente necessária aqui, mas vamos usá-la porque temos.

 html
< !DOCTYPE html >
< html lang="pt" >
 < cabeça >
 < meta charset="utf-8" >
 < link rel="Ícone de atalho" type="image/x-icon" href="/favicon.ico" >
 < título > Diretório - Clube dos Leitores de Quadrinhos! < /título >
 < link rel="stylesheet" href="/css/style.css" >
 < /cabeça >
 < corpo >
 < h1 > Diretório - Clube dos Leitores de Quadrinhos! < /h1 >
 < div >
 < a href="/signout" > saída < /a >
 < /div >
 < hora >
$(
cd ./app/comics
para id em *
Faz
echo " < div >< a href='/comics/$id' > $id < /a >< /div > "
feito
)
 < /corpo >
< /html >

Aqui temos html e bash sendo misturados para gerar html. Estamos percorrendo todos os arquivos em app/comics e criando um link para cada um.

O ponto final dos problemas

Agora que temos uma lista de quadrinhos, vamos configurar as listagens de capítulos específicos para um quadrinho. Agora precisamos ter alguma dinamicidade para que possamos lidar com diferentes histórias em quadrinhos com apenas uma função.

 bash
declare -A function_dictionary=(
[login]=login
[saída]=saída
["^quadrinhos$"]=quadrinhos
["^quadrinhos/(.*)"]=problemas
)

Aqui temos nossa primeira correspondência de regex em nosso dicionário de funções. Também temos um grupo de captura. Esta rota lidará com qualquer coisa no formato de quadrinhos/ALGO.

Vamos ver como isso funciona em ação!

 bash
questões() {
check_session
comic_name="${BASH_REMATCH[1]}"
render_template "./app/issues.html"
send_html "$modelo"
}

Aqui analisamos o nome do quadrinho usando BASH_REMATCH na posição 1. Isso ocorre porque quando percorremos o dicionário de funções em nosso manipulador de solicitações, fazemos =~ que faz um teste de regex. Se o teste for bem-sucedido, executamos a função. Se temos grupos de captura, também obtemos isso em BASH_REMATCH. Isso torna muito fácil lidar com rotas dinâmicas.

 html
< !DOCTYPE html >
< html lang="pt" >
 < cabeça >
 < meta charset="utf-8" >
 < link rel="Ícone de atalho" type="image/x-icon" href="/favicon.ico" >
 < título > Capítulos de $comic_name! < /título >
 < link rel="stylesheet" href="/css/style.css" >
 < /cabeça >
 < corpo >
 < h1 > Capítulos de $comic_name! < /h1 >
 < hora >
$(
cd ./app/comics/$comic_name
contador=0
para capítulo em *
Faz
contador=$((contador+1))
echo " < div >< a href='/comics/$comic_name/$counter' > $capítulo < /a >< /div > "
feito
)
 < /corpo >
< /html >

Nosso template também tem acesso a variáveis ​​que definimos em nossa função issues para que possamos cd no quadrinho correto que queremos ler e obter uma lista de todos os capítulos dentro dessa pasta.

Voilá! Agora temos listas de capítulos.

A página do problema

Finalmente podemos chegar à última tela do nosso leitor de quadrinhos, que é a página da edição. Esta página mostrará as imagens do capítulo que queremos ler. Aqui precisamos analisar 2 variáveis ​​do link da página de problemas. Precisamos do nome do quadrinho e do número do capítulo.

 bash
...
declare -A function_dictionary=(
[login]=login
[saída]=saída
["^quadrinhos$"]=quadrinhos
["^quadrinhos/(.*)"]=problemas
["^quadrinhos/(. *)/(.* )"]=problema
)
...

Adicionamos uma rota final para nossa página de problemas e definimos o regex para ter 2 grupos de captura, pois queremos capturar o nome do quadrinho e o número do capítulo.

 bash
...
questão() {
check_session
comic_name="${BASH_REMATCH[1]}"
issue_number="${BASH_REMATCH[2]}"
render_template "./app/issue.html"
send_html "$modelo"
}
...

Podemos acessar o nome do quadrinho através do primeiro BASH_REMATCH e o segundo tem o número da edição. Com esses 2 dados podemos renderizar a página final.

 html
< !DOCTYPE html >
< html lang="pt" >
 < cabeça >
 < meta charset="utf-8" >
 < link rel="Ícone de atalho" type="image/x-icon" href="/favicon.ico" >
 < título > $comic_name - Emissão $issue_number < /título >
 < link rel="stylesheet" href="/css/style.css" >
 < script type="text/javascript" src="/js/issue.js" >< /script >
 < /cabeça >
 
 < corpo >
 < h1 > $comic_name - Emissão $issue_number < /h1 >
 < hora >
$(
files=(./app/comics/"$comic_name"/*)
folder=("${files["$issue_number-1"]}"/*)
 
if [ -d $]
então
pasta=("$"/*)
fi
 
contador=0
para página em ${folder[@]}
Faz
contador=$((contador+1))
page=$(echo "$page" | sed 's/ \.\/ app//g')
if [[ "$contador" -le 3 ]]
então
echo " < div >< img src='$page' altura=1400 >< /div > "
senão
echo " < div >< img realsrc='$page' altura=1400 >< /div > "
fi
feito
)
 < /corpo >
< /html >

Esta página é um pouco mais complicada, pois estamos carregando imagens e precisamos lidar com os caminhos corretamente para que tudo funcione.

Voilá! Agora devemos ser capazes de navegar /login e passar por todo o login e ir para os quadrinhos e começar a ler. Agora temos um pequeno servidor web escrito inteiro em bash!


os anos desperdiçados, a juventude desperdiçada

Comentários



Faça o login para enviar uma mensagem