Quero usar a ativação do soquete systemd para iniciar um serviço sempre que uma solicitação HTTP for recebida no soquete de escuta. A mensagem recebida não é importante e pode ser descartada pelo systemd ou pelo serviço que ele inicia. Consegui fazer isso, mas não sem que o remetente HTTP travasse com erros de Connection reset by peer
. Gostaria de satisfazer o cliente HTTP remetente, fechando soquetes ou trocas HTTP corretamente.
Primeiro, tenho uma dúvida se devo usar Accept=yes
ou no
no .socket
arquivo. Por exemplo:
echo.socket
:
[Unit]
Description=Example echo socket
[Socket]
ListenStream=127.0.0.1:33300
Accept=yes
[Install]
WantedBy=sockets.target
Em man systemd.socket
, é sugerido que
Por motivos de desempenho, é recomendado escrever novos daemons apenas de uma forma que seja adequada para
Accept=no
.
No entanto, acho que o nome Accept=
implica se a conexão de entrada no soquete foi aceita ou não (acho que chamando accept()
no nível do kernel), porque Accept=no
a responsabilidade recai sobre o serviço iniciado para aceitar e manipular conexões no soquete. Veja, por exemplo, esta pergunta e sua resposta. Eu realmente gostaria que pudesse seguir a recomendação e usar a página de manual Accept=no
, mas a resposta na pergunta mencionada implica que eu precisaria invocar algum tipo de servidor TCP, que é uma curva de aprendizado adicional e uma camada de complexidade que eu gostaria de evitar se possível. (Qualquer resposta nesse sentido certamente seria aceitável.)
Então, suponha que eu use Accept=yes
e também tenha [email protected]
:
[Unit]
Description=echo service
[Service]
ExecStart=sh -c 'echo -e "HTTP/1.1 200 OK\nContent-Type: text/plain; charset=utf-8\nConnection: close\n\nOK"'
StandardInput=socket
StandardOutput=socket
O envio de solicitações HTTP funciona para ativar o serviço, mas o cliente não gosta:
$ curl 'http://127.0.0.1:33300'
OK
curl: (56) Recv failure: Connection reset by peer
$ journalctl --user -e
Jun 13 14:02:59 myhost systemd[906]: Started echo service (127.0.0.1:39908).
(Tente também, por exemplo, python -c "import requests; r = requests.get('http://127.0.0.1:33300'); print(r.status_code)"
.)
Um simples socat
sem HTTP parece sair sem problemas.
$ socat - TCP:127.0.0.1:33300
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Connection: close
OK
Não sei qual é o problema aqui, se o systemd fechar a conexão muito abruptamente para a troca HTTP, se a troca HTTP estiver faltando alguma mensagem extra de fechamento ou qualquer outra coisa. E, como mencionado acima, ainda estou curioso sobre Accept=no
soluções.
O problema é que o curl mantém seu lado da conexão aberto após o envio da solicitação e, como você nunca lê a solicitação no seu "servidor", isso leva ao erro "Conexão redefinida por ponto".
A solução mais simples é escrever o seu “servidor” para que ele leia a solicitação antes de enviar a resposta. Acho que a maneira mais fácil de fazer isso é mudar do shell para o
awk
, assim:Formatado para facilitar a leitura, o script awk se parece com isto:
(Observe que no arquivo unitário temos que substituir
\n
por\\n
; caso contrário, o systemd substitui the\n
por uma nova linha literal.)Isso lê a solicitação até a primeira linha em branco e emite a resposta. Funciona como você esperaria:
Alternativamente, você pode modificar a solução baseada em shell para adicionar um
Content-length
cabeçalho; isso permitecurl
saber quando recebeu a resposta completa:Isso também funcionará sem erros.