From f4b8c0df041b063c0b47d2ec6c818a9c202fd833 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Wed, 15 Apr 2020 09:54:10 -0700 Subject: Re-namespacing Moving away from the DNS-driven namespacing toward more condensed names, mostly because I don't like typing so much. --- Run/Que/Prod.nix | 44 --------- Run/Que/Server.hs | 240 -------------------------------------------------- Run/Que/Server.nix | 46 ---------- Run/Que/Website.hs | 126 -------------------------- Run/Que/Website.nix | 59 ------------- Run/Que/apidocs.md | 3 - Run/Que/client.py | 149 ------------------------------- Run/Que/index.md | 73 --------------- Run/Que/quescripts.md | 50 ----------- Run/Que/style.css | 136 ---------------------------- Run/Que/tutorial.md | 53 ----------- 11 files changed, 979 deletions(-) delete mode 100644 Run/Que/Prod.nix delete mode 100644 Run/Que/Server.hs delete mode 100644 Run/Que/Server.nix delete mode 100644 Run/Que/Website.hs delete mode 100644 Run/Que/Website.nix delete mode 100644 Run/Que/apidocs.md delete mode 100755 Run/Que/client.py delete mode 100644 Run/Que/index.md delete mode 100644 Run/Que/quescripts.md delete mode 100644 Run/Que/style.css delete mode 100644 Run/Que/tutorial.md (limited to 'Run/Que') diff --git a/Run/Que/Prod.nix b/Run/Que/Prod.nix deleted file mode 100644 index 97749c8..0000000 --- a/Run/Que/Prod.nix +++ /dev/null @@ -1,44 +0,0 @@ -{ config, pkgs, lib, ... }: -{ - imports = [ ]; - boot.loader.grub.device = "/dev/vda"; - fileSystems."/" = { device = "/dev/vda1"; fsType = "ext4"; }; - networking.firewall.allowedTCPPorts = [ 22 80 443 ]; - services.que-server = { - enable = true; - port = 80; - package = pkgs.que-server; - }; - services.que-website = { - enable = true; - namespace = "_"; - package = pkgs.que-website; - }; - networking = { - nameservers = [ - "67.207.67.2" - "67.207.67.3" - ]; - defaultGateway = "157.245.224.1"; - defaultGateway6 = "2604:a880:2:d1::1"; - dhcpcd.enable = false; - usePredictableInterfaceNames = lib.mkForce true; - interfaces = { - eth0 = { - ipv4.addresses = [ - { address="157.245.236.44"; prefixLength=20; } - { address="10.46.0.5"; prefixLength=16; } - ]; - ipv6.addresses = [ - { address="2604:a880:2:d1::a2:5001"; prefixLength=64; } - { address="fe80::7892:a5ff:fec6:dbc3"; prefixLength=64; } - ]; - ipv4.routes = [ { address = "157.245.224.1"; prefixLength = 32; } ]; - ipv6.routes = [ { address = "2604:a880:2:d1::1"; prefixLength = 32; } ]; - }; - }; - }; - services.udev.extraRules = '' - ATTR{address}=="7a:92:a5:c6:db:c3", NAME="eth0" - ''; -} diff --git a/Run/Que/Server.hs b/Run/Que/Server.hs deleted file mode 100644 index 0fc9fd1..0000000 --- a/Run/Que/Server.hs +++ /dev/null @@ -1,240 +0,0 @@ -{-# LANGUAGE NoImplicitPrelude #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE DeriveGeneric #-} - --- | Interprocess communication --- --- Prior art: --- - --- - --- - --- - sorta: and --- --- : exe que-server --- --- : dep async --- : dep envy --- : dep protolude --- : dep scotty --- : dep stm --- : dep unagi-chan --- : dep unordered-containers -module Run.Que.Server - ( main - ) -where - -import Alpha hiding ( Text - , get - , gets - , modify - , poll - ) -import qualified Control.Concurrent.Go as Go -import qualified Control.Concurrent.STM as STM -import qualified Control.Exception as Exception -import Control.Monad.Reader ( MonadTrans ) -import qualified Data.ByteString.Builder.Extra as Builder -import qualified Data.ByteString.Lazy as BSL -import Data.HashMap.Lazy ( HashMap ) -import qualified Data.HashMap.Lazy as HashMap -import qualified Data.Text.Encoding as Encoding -import Data.Text.Lazy ( Text - , fromStrict - ) -import qualified Data.Text.Lazy.IO as Text -import qualified Network.HTTP.Types.Status as Http -import qualified Network.Wai as Wai -import qualified Network.Wai.Handler.Warp as Warp -import Network.Wai.Middleware.RequestLogger - ( logStdout ) -import qualified System.Envy as Envy -import qualified System.Exit as Exit -import qualified Web.Scotty.Trans as Scotty - -main :: IO () -main = Exception.bracket startup shutdown <| uncurry Warp.run - where - startup = Envy.decodeEnv >>= \case - Left e -> Exit.die e - Right c -> do - sync <- STM.newTVarIO initialAppState - let runActionToIO m = runReaderT (runApp m) sync - waiapp <- Scotty.scottyAppT runActionToIO routes - putText <| "port:" <> (show <| quePort c) - return (quePort c, waiapp) - shutdown :: a -> IO a - shutdown = pure . identity - -newtype App a = App - { runApp :: ReaderT (STM.TVar AppState) IO a - } - deriving (Applicative, Functor, Monad, MonadIO, MonadReader - (STM.TVar AppState)) - -data AppState = AppState - { ques :: HashMap Namespace Quebase - } - -initialAppState :: AppState -initialAppState = AppState { ques = mempty } - -data Config = Config - { quePort :: Warp.Port -- ^ QUE_PORT - } deriving (Generic, Show) - -instance Envy.DefConfig Config where - defConfig = Config 3000 - -instance Envy.FromEnv Config - -routes :: Scotty.ScottyT Text App () -routes = do - Scotty.middleware logStdout - - let quepath = "^\\/([[:alnum:]_]+)\\/([[:alnum:]._/]*)$" - let namespace = "^\\/([[:alnum:]_]+)\\/?$" -- matches '/ns' and '/ns/' but not '/ns/path' - - -- | GET /index.html - Scotty.get (Scotty.literal "/index.html") <| Scotty.redirect "/_/index" - Scotty.get (Scotty.literal "/") <| Scotty.redirect "/_/index" - - -- | GET /_/dash - Scotty.get (Scotty.literal "/_/dash") <| do - authkey <- fromMaybe "" Scotty.stream $ streamQue q - _ -> do - r <- liftIO <| Go.read q - Scotty.html <| fromStrict <| Encoding.decodeUtf8 r - - -- | POST que - -- - -- Put a value on a que. Returns immediately. - Scotty.post (Scotty.regex quepath) <| do - authkey <- fromMaybe "" > Scotty.text "not allowed: _ is a reserved namespace" - >> Scotty.finish - guardNs ns ["pub", "_"] - -- passed all auth checks - app . modify <| upsertNamespace ns - q <- app <| que ns qp - qdata <- Scotty.body - _ <- liftIO <| Go.write q <| BSL.toStrict qdata - return () - --- | Given `guardNs ns whitelist`, if `ns` is not in the `whitelist` --- list, return a 405 error. -guardNs :: Text -> [Text] -> Scotty.ActionT Text App () -guardNs ns whitelist = when (not <| ns `elem` whitelist) <| do - Scotty.status Http.methodNotAllowed405 - Scotty.text - <| "not allowed: use 'pub' namespace or signup to protect '" - <> ns - <> "' at https://que.run" - Scotty.finish - --- | recover from a scotty-thrown exception. -(!:) - :: Scotty.ActionT Text App a -- ^ action that might throw - -> (Text -> Scotty.ActionT Text App a) -- ^ a function providing a default response instead - -> Scotty.ActionT Text App a -(!:) = Scotty.rescue - --- | Forever write the data from 'Que' to 'Wai.StreamingBody'. -streamQue :: Que -> Wai.StreamingBody -streamQue q write _ = Go.mult q >>= loop - where - loop c = - Go.tap c - >>= (write . Builder.byteStringInsert) - >> (write <| Builder.byteStringInsert "\n") - >> loop c - --- | Gets the thing from the Hashmap. Call's 'error' if key doesn't exist. -grab :: (Eq k, Hashable k) => k -> HashMap k v -> v -grab = flip (HashMap.!) - --- | Inserts the namespace in 'AppState' if it doesn't exist. -upsertNamespace :: Namespace -> AppState -> AppState -upsertNamespace ns as = if HashMap.member ns (ques as) - then as - else as { ques = HashMap.insert ns mempty (ques as) } - --- | Inserts the que at the proper 'Namespace' and 'Quepath'. -insertQue :: Namespace -> Quepath -> Que -> AppState -> AppState -insertQue ns qp q as = as { ques = newQues } - where - newQues = HashMap.insert ns newQbase (ques as) - newQbase = HashMap.insert qp q <| grab ns <| ques as - -extract :: Scotty.ActionT Text App (Namespace, Quepath) -extract = do - ns <- Scotty.param "1" - path <- Scotty.param "2" - return (ns, path) - --- | A synonym for 'lift' in order to be explicit about when we are --- operating at the 'App' layer. -app :: MonadTrans t => App a -> t App a -app = lift - --- | Get something from the app state -gets :: (AppState -> b) -> App b -gets f = ask >>= liftIO . STM.readTVarIO >>= return . f - --- | Apply a function to the app state -modify :: (AppState -> AppState) -> App () -modify f = ask >>= liftIO . atomically . flip STM.modifyTVar' f - -type Namespace = Text -- ^ housing for a set of que paths -type Que = Go.Channel Message -- ^ a que is just a channel of bytes -type Quepath = Text -- ^ any path can serve as an identifier for a que -type Message = ByteString -- ^ any opaque data -type Quebase = HashMap Quepath Que -- ^ a collection of ques - --- | Lookup or create a que -que :: Namespace -> Quepath -> App Que -que ns qp = do - _ques <- gets ques - let qbase = grab ns _ques - queExists = HashMap.member qp qbase - if queExists - then return <| grab qp qbase - else do - c <- liftIO <| Go.chan 1 - modify (insertQue ns qp c) - gets ques /> grab ns /> grab qp diff --git a/Run/Que/Server.nix b/Run/Que/Server.nix deleted file mode 100644 index e326483..0000000 --- a/Run/Que/Server.nix +++ /dev/null @@ -1,46 +0,0 @@ -{ options -, lib -, config -, pkgs -, modulesPath -}: - -let - cfg = config.services.que-server; -in -{ - options.services.que-server = { - enable = lib.mkEnableOption "Enable the que-server service"; - port = lib.mkOption { - type = lib.types.int; - default = 3000; - description = '' - The port on which que-server will listen for - incoming HTTP traffic. - ''; - }; - package = lib.mkOption { - type = lib.types.package; - description = "que-server package to use"; - }; - }; - config = lib.mkIf cfg.enable { - systemd.services.que-server = { - path = [ cfg.package ]; - wantedBy = [ "multi-user.target" ]; - script = '' - ${cfg.package}/bin/que-server - ''; - description = '' - Que server - ''; - serviceConfig = { - Environment = ["QUE_PORT=${toString cfg.port}"]; - KillSignal = "INT"; - Type = "simple"; - Restart = "on-abort"; - RestartSec = "1"; - }; - }; - }; -} diff --git a/Run/Que/Website.hs b/Run/Que/Website.hs deleted file mode 100644 index 52e46f9..0000000 --- a/Run/Que/Website.hs +++ /dev/null @@ -1,126 +0,0 @@ -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE NoImplicitPrelude #-} -{-# LANGUAGE LambdaCase #-} - --- | spawns a few processes that serve the que.run website --- --- : exe que-website --- --- : dep async --- : dep config-ini --- : dep process --- : dep protolude --- : dep req -module Run.Que.Website - ( main - ) -where - -import Alpha -import qualified Control.Concurrent.Async as Async -import qualified Data.ByteString.Char8 as BS -import qualified Data.Ini.Config as Config -import qualified Data.Text as Text -import Data.Text.Encoding ( encodeUtf8 ) -import qualified Data.Text.IO as Text -import Network.HTTP.Req -import qualified System.Directory as Directory -import System.Environment as Environment -import qualified System.Exit as Exit -import System.FilePath ( () ) -import qualified System.Process as Process - -main :: IO () -main = do - (src, ns) <- Environment.getArgs >>= \case - [src] -> return (src, "_") -- default to _ ns which is special - [src, ns] -> return (src, Text.pack ns) - _ -> Exit.die "usage: que-website [namespace]" - mKey <- getKey ns - putText $ "serving " <> Text.pack src <> " at " <> ns - run mKey ns $ Sources { index = src "index.md" - , client = src "client.py" - , quescripts = src "quescripts.md" - , style = src "style.css" - , apidocs = src "apidocs.md" - , tutorial = src "tutorial.md" - } - -getKey :: Namespace -> IO (Maybe Key) -getKey ns = do - home <- Directory.getHomeDirectory - let file = home ".config" "que.conf" - exists <- (Directory.doesFileExist file) - unless exists <| panic <| "not found: " <> Text.pack file - conf <- Text.readFile file - print (home ".config" "que.conf") - auth ns - |> Config.parseIniFile conf - |> either errorParsingConf identity - |> return - -errorParsingConf :: error -errorParsingConf = panic "could not parse ~/.config/que.conf" - -data Sources = Sources - { index :: FilePath - , quescripts :: FilePath - , client :: FilePath - , style :: FilePath - , tutorial :: FilePath - , apidocs :: FilePath - } - -type Namespace = Text -type Key = Text - -auth :: Namespace -> Config.IniParser (Maybe Key) -auth "pub" = pure Nothing -auth ns = Config.sectionMb ns <| Config.field "key" - -run :: Maybe Key -> Text -> Sources -> IO () -run key ns Sources {..} = Async.runConcurrently actions >> return () - where - actions = traverse - Async.Concurrently - [ forever <| toHtml index >>= serve key ns "index" - , forever <| toHtml quescripts >>= serve key ns "quescripts" - , forever <| BS.readFile client >>= serve key ns "client" - , forever <| toHtml tutorial >>= serve key ns "tutorial" - , forever <| toHtml apidocs >>= serve key ns "apidocs" - ] - toHtml :: FilePath -> IO ByteString - toHtml md = - BS.pack - <$> Process.readProcess - "pandoc" - [ "--include-in-header" - , style - , "-i" - , md - , "--from" - , "markdown" - , "--to" - , "html" - ] - [] - -serve :: Maybe Key -> Namespace -> Text -> ByteString -> IO () -serve Nothing "pub" path content = runReq defaultHttpConfig $ do - _ <- req POST - (http "que.run" /: "pub" /: path) - (ReqBodyBs content) - ignoreResponse - mempty - liftIO $ return () -serve Nothing p _ _ = panic <| "no auth key provided for ns: " <> p -serve (Just key) ns path content = runReq defaultHttpConfig $ do - let options = - header "Authorization" (encodeUtf8 key) <> responseTimeout maxBound - _ <- req POST - (http "que.run" /: ns /: path) - (ReqBodyBs content) - ignoreResponse - options - liftIO $ return () diff --git a/Run/Que/Website.nix b/Run/Que/Website.nix deleted file mode 100644 index 6a24d9d..0000000 --- a/Run/Que/Website.nix +++ /dev/null @@ -1,59 +0,0 @@ -{ options -, lib -, config -, pkgs -, modulesPath -}: - -let - cfg = config.services.que-website; - static = pkgs.stdenv.mkDerivation { - src = ./.; - name = "que-website-static"; - installPhase = '' - mkdir -p $out - cp ${./apidocs.md} $out/apidocs.md - cp ${./index.md} $out/index.md - cp ${./quescripts.md} $out/quescripts.md - cp ${./style.css} $out/style.css - cp ${./tutorial.md} $out/tutorial.md - cp ${./client.py} $out/client.py - ''; - }; -in -{ - options.services.que-website = { - enable = lib.mkEnableOption "Enable the que-website service"; - namespace = lib.mkOption { - type = lib.types.str; - default = "_"; - description = '' - The que namespace on which que-website will broadcast. - ''; - }; - package = lib.mkOption { - type = lib.types.package; - description = "que-website package to use"; - }; - }; - config = lib.mkIf cfg.enable { - systemd.services.que-website = { - path = [ cfg.package pkgs.pandoc ]; - wantedBy = [ "multi-user.target" ]; - script = '' - ${cfg.package}/bin/que-website ${static} ${cfg.namespace} - ''; - description = '' - Que website server - ''; - serviceConfig = { - User = "root"; - Environment = "HOME=/root"; - KillSignal = "INT"; - Type = "simple"; - Restart = "on-abort"; - RestartSec = "1"; - }; - }; - }; -} diff --git a/Run/Que/apidocs.md b/Run/Que/apidocs.md deleted file mode 100644 index f400889..0000000 --- a/Run/Que/apidocs.md +++ /dev/null @@ -1,3 +0,0 @@ -% que.run Api Docs - -coming soon diff --git a/Run/Que/client.py b/Run/Que/client.py deleted file mode 100755 index 3d9291d..0000000 --- a/Run/Que/client.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python3 -""" -simple client for que.run -""" - -import argparse -import configparser -import http.client -import os -import subprocess -import sys -import time -import urllib.parse -import urllib.request as request - -MAX_TIMEOUT = 99999999 # basically never timeout - - -def auth(args): - ns = args.target.split("/")[0] - if ns == "pub": - return None - else: - conf_file = os.path.expanduser("~/.config/que.conf") - if not os.path.exists(conf_file): - sys.exit("you need a ~/.config/que.conf") - cfg = configparser.ConfigParser() - cfg.read(conf_file) - return cfg[ns]["key"] - - -def send(args): - "Send a message to the que." - key = auth(args) - data = args.infile - req = request.Request(f"{args.host}/{args.target}") - req.add_header("User-AgenT", "Que/Client") - if key: - req.add_header("Authorization", key) - if args.serve: - while not time.sleep(1): - request.urlopen(req, data=data, timeout=MAX_TIMEOUT) - - else: - request.urlopen(req, data=data, timeout=MAX_TIMEOUT) - - -def recv(args): - "Receive a message from the que." - - def _recv(_req): - msg = autodecode(_req.read()) - print(msg) - if args.then: - subprocess.run( - args.then.replace("\msg", msg).replace("\que", args.target), shell=True - ) - - params = urllib.parse.urlencode({"poll": args.poll}) - req = request.Request(f"{args.host}/{args.target}?{params}") - req.add_header("User-Agent", "Que/Client") - key = auth(args) - if key: - req.add_header("Authorization", key) - with request.urlopen(req) as _req: - if args.poll: - while not time.sleep(1): - _recv(_req) - else: - _recv(_req) - - -def autodecode(b): - """Attempt to decode bytes `b` into common codecs, preferably utf-8. If - no decoding is available, just return the raw bytes. - - For all available codecs, see: - - - """ - codecs = ["utf-8", "ascii"] - for codec in codecs: - try: - return b.decode(codec) - except UnicodeDecodeError: - pass - return b - - -def get_args(): - cli = argparse.ArgumentParser(description=__doc__) - cli.add_argument( - "--host", default="http://que.run", help="where que-server is running" - ) - cli.add_argument( - "--poll", default=False, action="store_true", help="stream data from the que" - ) - cli.add_argument( - "--then", - help=" ".join( - [ - "when polling, run this shell command after each response,", - "presumably for side effects," - "replacing '\que' with the target and '\msg' with the body of the response", - ] - ), - ) - cli.add_argument( - "--serve", - default=False, - action="store_true", - help=" ".join( - [ - "when posting to the que, do so continuously in a loop.", - "this can be used for serving a webpage or other file continuously", - ] - ), - ) - cli.add_argument( - "target", help="namespace and path of the que, like 'ns/path/subpath'" - ) - cli.add_argument( - "infile", - nargs="?", - type=argparse.FileType("rb"), - help="data to put on the que. Use '-' for stdin, otherwise should be a readable file", - ) - return cli.parse_args() - - -if __name__ == "__main__": - args = get_args() - try: - if args.infile: - send(args) - else: - recv(args) - except KeyboardInterrupt: - sys.exit(0) - except urllib.error.HTTPError as e: - print(e) - sys.exit(1) - except http.client.RemoteDisconnected as e: - print("disconnected... retrying in 5 seconds") - time.sleep(5) - if args.infile: - send(args) - else: - recv(args) diff --git a/Run/Que/index.md b/Run/Que/index.md deleted file mode 100644 index a9db12e..0000000 --- a/Run/Que/index.md +++ /dev/null @@ -1,73 +0,0 @@ -% que.run - -que.run is the concurrent, async runtime in the cloud - - - runtime concurrency anywhere you have a network connection - - multilanguage communicating sequential processes - - add Go-like channels to any language - - connect your microservices together with the simplest possible - plumbing - - async programming as easy as running two terminal commands - -HTTP routes on `que.run` are Golang-like channels with a namespace and a -path. For example: `https://que.run/pub/path/subpath`. - -## Quickstart - -There is a simple script `que` that acts as a client you can use to -interact with the `que.run` service. - -Download it to somewhere on your `$PATH` and make it executable: - - curl https://que.run/_/client > ~/bin/que - chmod +x ~/bin/que - que --help - -The client requires a recent version of Python 3. - -## Powerup - -que.run is free for limited use, but the real power of an asynchronous, -concurrent runtime in the cloud is unlocked with some extra power-user -features. - -- Free - - security by obscurity - - all protocols and data formats supported - - bandwidth and message sizes limited - - concurrent connections limited - - request rate limited -- Power - - protect your data with private namespaces - - remove bandwidth and size limits - - private dashboard to see all of your active ques - - 99.999% uptime -- Pro - - add durability to your ques so messages are never lost - - powerful batch api - - incredible query api - - Linux FUSE filesystem integration -- Enterprise - - all of the Power & Pro features - - on-prem deployment - - advanced que performance monitoring - - SLA for support from que.run experts - -Email `ben@bsima.me` if you want to sign up for the Power, Pro, or -Enterprise packages. - -## Quescripts - -We are collecting a repository of scripts that make awesome use of que: - -- remote desktop notifications -- two-way communication with your phone -- ephemeral, serverless chat rooms -- collaborative jukebox - -See the scripts - -## Docs - -- [tutorial](/_/tutorial) -- [api docs](/_/apidocs) diff --git a/Run/Que/quescripts.md b/Run/Que/quescripts.md deleted file mode 100644 index 9a2e6e0..0000000 --- a/Run/Que/quescripts.md +++ /dev/null @@ -1,50 +0,0 @@ -% Quescripts - -## Remote desktop notifications - -Lets say we are running a job that takes a long time, maybe we are -compiling or running a large test suite. Instead of watching the -terminal until it completes, or flipping back to check on it every so -often, we can create a listener that displays a popup notification when -the job finishes. - -In one terminal run the listener: - - que pub/notify --then "notify-send '\que' '\msg'" - -In some other terminal run the job that takes forever: - - runtests ; echo "tests are done" | que pub/notify - - -When terminal 2 succeeds, terminal 1 will print "tests are done", then -call the `notify-send` command, which displays a notification toast in -Linux with title "`pub/notify`" and content "`tests are done`". - -Que paths are multi-producer and multi-consumer, so you can add as many -terminals as you want. - -On macOS you could use: - - osascript -e 'display notification "\msg" with title "\que"' - -in place of notify-send. - -## Ephemeral, serverless chat rooms - -coming soon - -## Collaborative jukebox - -It's surprisingly easy to make a collaborative jukebox. - -First start up a music player: - - que --poll pub/music --then "playsong '\msg'" - -where `playsong` is a script that plays a file from data streaming to -`stdin`. For example [vlc](https://www.videolan.org/vlc/) does this when -you run it like `vlc -`. - -Then, anyone can submit songs with: - - que pub/music song.mp3 diff --git a/Run/Que/style.css b/Run/Que/style.css deleted file mode 100644 index f8d1ca4..0000000 --- a/Run/Que/style.css +++ /dev/null @@ -1,136 +0,0 @@ - - diff --git a/Run/Que/tutorial.md b/Run/Que/tutorial.md deleted file mode 100644 index 66ecd3c..0000000 --- a/Run/Que/tutorial.md +++ /dev/null @@ -1,53 +0,0 @@ -% que.run Tutorial - -## Ques - -A que is a multi-consumer, multi-producer channel available anywhere you -have a network connection. If you are familiar with Go channels, they -are pretty much the same thing. Put some values in one end, and take -them out the other end at a different time, or in a different process. - -Ques are created dynamically for every HTTP request you make. Here we -use the `que` client to create a new que at the path `pub/new-que`: - - que pub/new-que - -The `que` client is useful, but you can use anything to make the HTTP -request, for example here's the same thing with curl: - - curl https://que.run/pub/new-que - -These requests will block until a value is placed on the other -end. Let's do that now. In a separate terminal: - - echo "hello world" | que pub/new-que - - -This tells the `que` client to read the value from `stdin` and then send -it to `example/new-que`. Or with curl: - - curl https://que.run/pub/new-que -d "hello world" - -This will succeed immediately and send the string "`hello world`" over -the channel, which will be received and printed by the listener in the -other terminal. - -You can have as many producers and consumers attached to a channel as -you want. - -## Namespaces - -Ques are organized into namespaces, identified by the first fragment of -the path. In the above commands we used `pub` as the namespace, which is -a special publically-writable namespace. The other special namespace is -`_` which is reserved for internal use only. You can't write to the `_` -namespace. - -To use other namespaces and add authentication/access controls, you can -[sign up for the Power package](/_/index). - -## Events - -Just reading and writing data isn't very exciting, so let's throw in -some events. We can very quickly put together a job processor. - - que pub/new-que --then "./worker.sh '\msg'" -- cgit v1.2.3