5import Data.Array ((..))
6import Data.Either (Either(..))
7import Data.Int (fromString)
8import Data.Maybe (Maybe(..), fromMaybe)
9import Data.Newtype (un)
10import Data.Posix.Signal (Signal(..))
11import Data.Time.Duration (Seconds(..), fromDuration)
12import Data.Traversable (for)
14import Effect.Aff (Aff, effectCanceler, launchAff_, makeAff, parallel, sequential)
15import Effect.Aff.Class (liftAff)
16import Effect.Class (class MonadEffect, liftEffect)
17import Effect.Class.Console (info, log)
18import Foreign (unsafeToForeign)
19import JobQueue (EnqueueResult(..), NewJob(..), Queue, ResourcePool(..))
21import Middleware.JsonBodyParser (jsonBodyParser)
22import Node.Buffer (Buffer)
23import Node.Buffer as Buffer
24import Node.ChildProcess (ExecResult, defaultExecOptions, exec, kill, pid)
25import Node.Encoding (Encoding(..))
26import Node.Express.App (App, listenHttp, listenHttps, use, useExternal)
27import Node.Express.App as E
28import Node.Express.Handler (HandlerM(..), Handler)
29import Node.Express.Middleware.Static (static)
30import Node.Express.Request (getBody')
31import Node.Express.Response (setStatus)
32import Node.Express.Response as Response
33import Node.Express.Types (Request, Response)
34import Node.FS.Aff (readTextFile)
35import Node.HTTP (Server)
36import Node.OS (numCpus)
37import Node.Process (lookupEnv)
38import Playground.Playground (Folder(..), copy)
39import PscIdeClient (PscIdeConnection, compileCode, getFolder, mkConnection)
40import Shared.Json (readAff)
41import Shared.Models.Body (CompileRequest, RunResult, CompileResult)
42import Shared.Models.Body as Body
43import Simple.JSON (readJSON, read_, write)
45toBody ∷ ∀ r m. MonadEffect m => { stdout ∷ Buffer, stderr ∷ Buffer | r } -> m RunResult
49 stdout <- Buffer.toString UTF8 result.stdout
50 stderr <- Buffer.toString UTF8 result.stderr
52 (code ∷ Maybe Int) = asErrorWithCode result >>= _.code
53 in { code, stdout, stderr } ∷ RunResult
56 = { code ∷ Maybe Int }
58asErrorWithCode ∷ ∀ a. a -> Maybe ErrorWithCode
59asErrorWithCode = read_ <<< unsafeToForeign
61runCode ∷ Folder -> Aff ExecResult
62runCode folder = execCommand folder "node run.js"
64execCommand ∷ Folder -> String -> Aff ExecResult
65execCommand folder command =
66 makeAff \callback -> do
68 options = defaultExecOptions { cwd = Just (un Folder folder) }
69 childProcess <- exec command options (callback <<< Right)
70 pure $ effectCanceler ((log $ "Killing " <> show (pid childProcess)) *> kill SIGKILL childProcess)
72compileAndRunJob ∷ CompileRequest -> (Handler -> Aff Unit) -> NewJob PscIdeConnection
73compileAndRunJob json handle =
74 NewJob \jobId conn -> do
75 responseString <- compileCode json.code conn
76 case ((readJSON responseString) :: _ _ CompileResult) of
77 Right res | res.resultType == "error" -> do
78 handle $ setStatus 422
79 handle $ Response.send $ write res
81 runResult <- runCode (getFolder conn)
82 resultBody <- toBody runResult
83 handle $ Response.send $ write (resultBody ∷ Body.RunResult)
85 handle $ setStatus 500
86 log $ "Could not decode: " <> responseString <> "\nErrors: " <> show errs
87 handle $ Response.send $ write {}
89compileAndRunHandler ∷ Queue PscIdeConnection -> Handler
90compileAndRunHandler queue = do
92 json <- readAff body # liftAff
93 HandlerM \req res next -> do
95 handle = unHandler req res next
97 runJob = compileAndRunJob json handle
98 queueRes <- Q.enqueue runJob queue # liftEffect
100 Enqueued _ -> pure unit
104 Response.send $ write { error: "Queue full" }
106unHandler ∷ ∀ a. Request -> Response -> Effect Unit -> HandlerM a -> Aff a
107unHandler req res next (HandlerM h) = h req res next
110srcFolder = "../playground"
113destFolder = "../playgrounds/"
118 cpus <- numCpus # liftEffect
120 poolSize = max 2 (cpus - 1) -- use at least 2
122 mkFolder = Folder <<< (destFolder <> _) <<< show
124 connections <- for (0 .. poolSize) \n -> do
125 let folder = mkFolder n
127 copy srcFolder (un Folder folder)
128 mkConnection folder port
130 let pool = ResourcePool connections
134 , timeout: 10.0 # Seconds # fromDuration
137 serverSetup (makeApp q)
139makeApp ∷ Queue PscIdeConnection -> App
141 use $ static "assets"
142 useExternal jsonBodyParser
143 E.post "/compileAndRun" (compileAndRunHandler q)
145data CertificatesToUse
146 = UseLocalCertificates
147 | DoNotUseLocalCertificates
149parseCertificatesToUse ∷ String -> CertificatesToUse
150parseCertificatesToUse = case _ of
151 "LOCAL" -> UseLocalCertificates
152 _ -> DoNotUseLocalCertificates
154serverSetup ∷ App -> Aff Server
156 maybePortString <- lookupEnv "PORT" # liftEffect
158 port = maybePortString >>= fromString # fromMaybe 14188
159 useLocalCertificates <- fromMaybe DoNotUseLocalCertificates <<< map parseCertificatesToUse <$> lookupEnv "CERTS" # liftEffect
160 listen <- case useLocalCertificates of
161 UseLocalCertificates -> do
162 httpsOptions <- makeHttpsOptions
163 pure $ listenHttps app (port ∷ Int) httpsOptions
164 DoNotUseLocalCertificates -> pure $ listenHttp app port
165 liftEffect $ listen \_ -> info $ "psfp server started on port " <> show port
167 makeHttpsOptions = do
168 key <- readTextFile UTF8 "server.key"
169 cert <- readTextFile UTF8 "server.cert"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list