Skip to content

Allow stdin to be read without collecting it. #14901

Open
@sbrow

Description

Related problem

I was trying to create an HTTP server using only nushell and nmap's ncat, but because ncat doesn't send EOFs after each request, my request handler hung forever. I ended up having to use bash to separate the requests. 😢

Bash solution

main.sh

#!/usr/bin/env sh

# Spin up the HTTP server
ncat -lk -p 8080 -c './request_handler.sh' -v

request_handler.sh

#!/usr/bin/env sh

# Read lines until we hit an empty line
REQUEST=""
NL=$'\n'
while read -r line; do
    trline=$(echo "$line" | tr -d '\r\n')

    if [ -z "$trline" ]; then
      break
    fi

    REQUEST="${REQUEST}${trline}${NL}"
done

# Ignore the request body because I didn't need it for my use case.

echo "$REQUEST" | mod.nu

mod.nu

#!/usr/bin/env -S nu --stdin

# Actually handle the http requests
def main [] {
  let request = $in;
  let url = (
    'http://localhost'
    + (
      $request | lines | first | split row ' ' | get 1
    )
  ) | url parse

  if ($request | authorized) {
    respond 200 (handle $url)
  } else {
    respond 401 ({ message: "Unauthorized" })
  }
}

def authorized []: string -> bool {
  let headers = ($in | lines | skip 1 | take until { |line| $line == "" })

  $headers | any { $in =~ $"[aA]uthorization: Bearer ($env.SOME_SECRET_KEY)"}
}

def handle [
  url: record
] {
  # business code ellided...
}

def respond [
  code: int
  body: any
] {
$'HTTP/1.1 ($code) ($code | code to string)
Content-Type: application/json

($body | to json)'
}

def "code to string" []: int -> string {
  match $in {
    200 => 'OK'
    401 => 'Unauthorized'
  }
}

Describe the solution you'd like

I'd like to be able to read from stdin (either line-by-line or one byte at a time) without having to wait for an EOF byte. It seems that there is currently no way to do this.

Describe alternatives you've considered

  1. I tried reading from $in, but that appears to collect the results. It works with a minimal example, but doesn't appear to work if you keep the buffer open.
> 'foo
bar' | ./in_example.nu
foo
bar

in_example.nu

#!/usr/bin/env -S nu --stdin

def main [] {
  $in
}
  1. I tried reading with `input -u '\n', but that only accepted interactive input, not piped input.
> 'foo
bar' | ./input_example.nu
# Hangs forever

input_example.nu

#!/usr/bin/env -S nu --stdin

# input_example.nu

input -u '\n'

Additional context and details

My discord conversation (in #help)

Image

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-triageAn issue that hasn't had any proper look

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions