badgerati / pode Goto Github PK
View Code? Open in Web Editor NEWPode is a Cross-Platform PowerShell web framework for creating REST APIs, Web Sites, and TCP/SMTP servers
Home Page: https://badgerati.github.io/Pode
License: Other
Pode is a Cross-Platform PowerShell web framework for creating REST APIs, Web Sites, and TCP/SMTP servers
Home Page: https://badgerati.github.io/Pode
License: Other
So right now, if you define a static route:
route static '/assets' './assets''
then a call to http://localhost:8080/assets/icon.png
will simple get the static file from ./assets/icon.png
. However, if you just called http://localhost:8080/assets
then a 404 will be returned.
This change will make it so that calling http://localhost:8080/assets
, if it matches a defined static route, will instead return an index.html
/default.html
or any other variant if one exists within the directory.
Internally, there should be a defined list of files to check. This list should be customisable, either via a function or a config file - pode.json
?
A few old functions to write json/xml/view responses were made obsolete for more streamlined functions, in the Responses.ps1
file.
They were made obsolete a few versions ago, and now need to be removed - and for some of them, also removed from being exported.
Only an internal improvement, purely to set the logger
script at the end of a web-request to use the new endware
logic.
The idea behind this is to allow IP address and subnet masks to be allowed/denied, and any incoming requests that meet the requirement are allowed or denied access.
This will be under a new keyword of access
to be used within the Server
, examples include:
# allowing the local ip
access allow ip 127.0.0.1
# allow a subnet, will allow everything 10.10.0.0 - 10.10.0.255
access allow ip '10.10.0.0/24'
# deny an array of ips
access deny ip @('192.168.1.2', '192.168.1.3')
# deny every address
access deny ip all
The allow
list is checked first, followed by the deny
list. If the IP is in neither but we have an allow
list then auto-deny the IP.
Feature can maybe be extended later on to support content types (ie: access allow content 'text/plain'
)
Feature to support basic authentication using the session middleware. This will not be using the inbuilt AuthenticationSchemes on an HttpListener, and instead be built as middleware for extensibility.
In the Response.ps1
file, for the Write-ToResponseFromFile
function.
Currently if the file doesn't exist it returns a 404, beyond that it just attempts to get the content and write it to the web response. If the file happens to be inaccessible, then it should set the status 401
, rather than throwing a horrible error.
At the moment, routes can only be attached to a single method; this issue is, for now, to allow a new method of *
. This will allow a route to be attached onto every HTTP method.
The *
is also special, in that routes will first be checked against the main methods, and then finally - if one cannot be found - it will check the route against *
.
This will return hello on every method when calling
/hello
# return hello on every method
route * '/hello' {
json @{ 'value' = 'hello!' }
}
This will return "bonjour!" on
/hello
forget
, but "hello!" for every other method
# return bonjour on every get
route get '/hello' {
json @{ 'value' = 'bonjour!' }
}
# return hello on every method
route * '/hello' {
json @{ 'value' = 'hello!' }
}
As per the title, because frankly the readme is getting a little bit cluttered!! π
Allow the ability for Pode to listen on a specific IP rather than listening on everything (0.0.0.0
).
A new argument on the Server
will be created called IP
, which will allow you to specify an IP address to listen on; if no IP address is passed then Pode will listen on everything (default behaviour):
# Pode will listen on 127.0.0.2:8080
Server -IP 127.0.0.2 -Port 8080 {
# logic
}
# Pode will listen on everything
Server -Port 8080 {
# logic
}
Add 3 new functions of json
, xml
and view
to replace the Write-ViewResponse
functions.
A view
is always from a file and uses the view engine, but a json
/xml
can be an object/pure json or a file path that needs reading in.
Similar to the access
feature, include a basic rate-limiting feature to limit the number of requests over a period time from an IP/Subnet. When the limit is reached, return the HTTP Status of 429.
A simple example of this, to limit 127.0.0.1
to only 5 request every 10 seconds is:
Server -Port 8080 {
limit ip 127.0.0.1 5 10
}
Only 5 requests can be made over 10 seconds; if a 6+ call is made HTTP 429 is returned. After 10 seconds, the limit is reset to 0 for another 10 seconds
Subnets are a little different; you could either treat 10.0.0.0/24
to block each IP individually, or you can treat is as a whole and each IP the subnet covers share a single limit:
Server -Port 8080 {
# this will limit every IP in the subnet individually
limit ip '10.0.0.0/24' 5 10
# this will limit every IP in the subnet as one
limit ip '10.0.0.0/24' 5 10 -shared
}
Hi @Badgerati,
I have been looking for a way to dynamically reload a running Pode server when 'x' file changes. A la nodemon is able to do. Are you aware of a similar tool for PowerShell and therefore Pode?
Thank you very much.
To get the information from the header, the following code will work.
In the Start-WebServer, right after you define the content type:
[System.Collections.Specialized.NameValueCollection]$headers = $request.Headers
$HeadersArray = foreach ($key in $headers.AllKeys){
[array]$values = $headers.GetValues($key);
if($values.Length -gt 0) {
New-Object psobject -Property ([ordered]@{
HeaderName = $key
HeaderValue = $values[0]
})
}
}
And this to add it for the output.
$PodeSession.Web.Headers = $HeadersArray
If you have an external ps1 file, then at the moment just referencing it in your server script still won't allow routes/timers/etc to see those functions.
Possible solution here is to have a module (.psm1) which has those functions in, then use the ImportPSModule
on the runspace pools to allow runspaces to access those functions. ie:
Server {
script './path/to/module.psm1'
}
This will import the
module.psm1
onto each runspace pool
I'd like to have a Server timer running every X seconds that will pull some data and update a hash table. I would then like to read from that hash table when a specific api route is called.
I haven't been able to figure out a way to do that. I looked through the readme and the timer example, but that is more about how to create/set a timer, and not about the logic and variables within the timer. Any guidance would be appreciated.
I am trying to send a long base64 string as a parameter to the API.
When i reach +260 characters, the server returns a 400 bad request.
Found this, and is testing now.
https://support.microsoft.com/da-dk/help/820129/http-sys-registry-settings-for-windows
This feature comes from #20, and a code example via PowerShell can be found here: https://gallery.technet.microsoft.com/scriptcenter/Powershell-FileSystemWatche-dfd7084b
Server
block has the -Monitor
switch supplied?@Badgerati - Was wondering if you had any thoughts or plans on adding some logging to pode Servers in order to have a historic view of what is being requested, sourceip, ... along the lines of something we'd get if we ran an iis-based web/smtp server with basic logging enabled. I'd be willing to take a first crack at it if you'd like.
When using pode start
, it fails because it throws an error when attempting to import the Pode module into the runspaces:
Exception calling "ImportPSModule" with "1" argument(s): "Value cannot be null.
Parameter name: name"
At C:\Program Files\WindowsPowerShell\Modules\Pode\Tools\Session.ps1:136 char:5
+ $state.ImportPSModule((Get-Module -Name Pode).Path)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ArgumentNullException
New feature to support session middleware, such that it will check for/create new sessions on web requests, and store any related data in-mem or in a custom storage (redis/mongo/etc).
An example of setting up session middleware:
middleware (session @{
'Secret' = 'schwifty'; # secret-key used to sign session cookie
'Name' = 'pode.sid'; # session cookie name (def: pode.sid)
'Duration' = 120; # duration of the cookie, in seconds
'Extend' = $true; # extend the duration of the cookie on each call
'GenerateId' = { # custom SessionId generator (def: guid)
return [System.IO.Path]::GetRandomFileName()
};
'Store' = $null; # custom object with required methods (def: in-mem)
})
An example of using a session in a route to increase views counter:
route 'get' '/' {
param($s)
$s.Session.Data.Views++
json @{ 'Views' = $s.Session.Data.Views }
}
The counter will continue to increment on each call to the route until the session expires.
Really needs to be done at some point π
I've already started work on this in the async-content
branch.
Basic idea: Pode should be able to handle content requests asynchronously - ie, the files within the /public
directory. This is to help when large image files (or other files) are being returned, and to stop content requests building up and making them slower and slower over time.
The async part is achieved using Runspaces in PowerShell, and the number of Runspaces being created to handle content requests should be configurable - default and minimum is 1. Requests will be setup as a new runspace, and PowerShell will handle the queuing internally.
This will also include some basic performance tests using k6
.
Work on getting all requests to be handled async will be worked on separately in a different ticket (ie,
views
) - as this requires a bigger overhaul.
In order to start supporting features like authentication, Pode needs to support middleware
first. This will allow you run logic - in the order the middleware
is defined - on the Request and Response objects.
By default, Pode will have some pre-defined middleware that will run first (ie, access, rate limiting, static content routing, query string, and body parsing), and then the final route
that will be run (which will also support any intermediate middleware for logic like auth'ing a request - think passport
).
For defining new middleware
, the following could be a possible example in Server
:
middleware {
# $session will contain the Request and Response
param($session)
# check the requests HTTP protocol version
if ($session.Request.ProtocolVersion -ieq '1.1') {
# some logic
}
# if fail, set status and return $false (will halt and return immediately)
status 400
json @{'Message' = 'Failed'}
return $false
# on success return $true, and run next middleware
return $true
# if success but you want to stop processing other middleware/routes
return $false
}
If any
middleware
fails and returns$false
, then Pode should stop processing the request
For a route
, you could have:
# a normal route, with a method, path and logic
route get '/path' { # logic }
# route but with middleware that runs first
# if the middleware fails, the route logic is not run
route get '/path' { #middleware } { # logic }
# route with an array of middlewares
route get '/path' @({ #m1 }, { #m2 }) { #logic }
The order in which middlewares are run are as follows:
middleware
[run in order supplied]middleware
suppliedRight now the choco package downloads the module's source from GitHub; this change will package up the module itself into the choco package
I have files at:
/views/index.pode
/public/scripts/main.js.pode
In /views/index.pode, I use the following code to try and include /public/scripts/main.js.pode
$(include scripts/main)
or even using
$(include scripts/main.js.pode)
I get a "file not found at path" error.
A comment within Responses.ps1 on line 309 seems to indicate that only the views path is looked at:
# only look in the view directory
In the rest of the Include function, I don't see any code that would look through the public folder.
I am able to use the following successfully:
$(include ../public/scripts/main.js.pode)
Am I making a mistake somewhere?
The Dockerfile used currently is always targeting latest; this issue is simply to set it to use microsoft/powershell:ubuntu-xenial
. I've already done some testing with it, and this version works as normal.
This will allows temporary preview versions of Pode to use preview versions of PowerShell - aka, the issues seen with File Monitoring that are resolved with 6.1.0 currently in preview.
Right now the directories for view and public assets are coded to /views
and /public
respectively. This change will add a function to allow the altering of these directories.
Something like the following maybe?
paths assets '/assets'
paths views '/routes'
Like the Web and SMTP servers, the Service type of Server should really have also been updated to have its logic running in a runspace - not within the switch statement.
Right now the only way to edit the StatusCode of a response in a route is via:
$session.Response.StatusCode = 200
This update will make it easier via:
# just the status code
status 200
# status code and description
status 200 'some description'
Like how timers have -Limit
option, this will be added to schedules as well. This will allow schedules to run X times, and then be removed.
Also, a removal list will be added internal for schedules, much like how timers are removed when they've come to their last trigger.
Hi Matthew,
I really like your Pode repo. I have one challenge though. The documentation and some of the examples does not match the latest release available on DockerHub or/and the PowerShell module. For example the Pode and route functions are not in the latest public release. Will you update soon?
I guess I'll have to clone for now and go from there. But it will lovely to get an official release.
π
Hi @Badgerati,
In the Dockerfile at > https://github.com/Badgerati/Pode#docker one doesn't actually have to have the RUN mkdir....
line. As the line below, the COPY ....
will create folders on the destination, for every folder in the src folder. As long as the one remembers to have a trailing slash on dest
specified.
See more https://docs.docker.com/engine/reference/builder/#copy where that last line of the COPY section says > If <dest> doesnβt exist, it is created along with all missing directories in its path.
A minor improvement but I just felt like sharing it as I really like your Pode framework and because it will make the resulting image, from running Docker build....
smaller. The fewer lines in the Dockerfile the fewer layers in the image. Is the general rule of thumb as far as I have understood.
Have a great evening.
Normally an internal server restart is triggered when the -FileMonitor
flag is set, and file changes are detected. This change will allow the restart to be triggered from the console when Ctrl+R
is pressed.
When an internal restart is triggered due to a file change - if we're monitoring - then the runspace pools need to be disposed and recreated. This will also include recreating the shared session state as well.
The reason for this, is the runspace pools won't update any changed modules and will continue to use older versions. (mostly since the script
function was introduced).
Add the ability to create async timers that will run in a separate runspace along the core server logic.
Timers should be able to run indefinitely, or for a limited number of runs.
When ctrl-c is hit, or the app errors out, dispose and close the extra runspaces.
Add a new helper response function to attach a file to the response that can be downloaded; files should come from the Public directory, much like normal content requests.
ie, doing the following in a route
would start a download for the public/downloads/install.exe
file:
route get '/installer' {
param($s)
attach 'downloads/install.exe'
}
Repeatable tasks at the moment are supported via timer
s that run at fixed intervals/seconds.
This feature is to support a new schedule
which works much like timer
s, but using cron expressions, instead of fixed seconds.
This could replace
timer
s at some point, as they're effectively their successor
Basic cron expressions will be supported, with more advanced cron expression syntax being added later on. Some predefined expressions will be setup, like @hourly
, etc.
Example of a schedule
:
Server {
# schedule minutely using predefined cron
schedule 'predefined' '@minutely' {
# logic
}
# schedule to run every tuesday at midnight
schedule 'tuesdays' '0 0 * * TUE' {
# logic
}
}
PowerShell 6.1.0 has now been released, which means the image in the Dockerfile can be updated to the following:
mcr.microsoft.com/powershell:6.1.0-ubuntu-16.04
I've already done some testing with it, and everything works all right - including the ability for internal restarts to now function properly!
Invoke-Build: https://github.com/nightroman/Invoke-Build
This is to refactor the build, test, docs, and packing scripts all using Invoke-Build
.
Scripts should also work cross-platform, ie the docs dependencies
Is there a reason wht the function Status is not exported?
Is it posible to add status code in all of the responses?
I have been trying to display some images, but it is not working.
The workaround that i found was to base64 encode them in a Pode file.
img src="data:image/png;base64,$([convert]::ToBase64String((Get-Content image.png -Encoding byte)))" alt="png"
It is working, but i have to rewrite some pages.
Do you thing it will be fixed in a newer version?
I am on the newest commit: 9132d7d
Hi @Badgerati,
So there I was. Starting the server via PowerShell dot-sourcing. However, not in the folder of the .PS1 being dot-sourced. But from several levels up in the folder hierarchy.
Issue
When I do this, Pode cannot locate the "Whatever.html" file I want loaded as this >
Line 388 in 52b00ff
$global:MyInvocation.MyCommand.Path
magic inside the Responses.ps1 file. Now my "Whatever.html" file could be located but other files, such as CSS and JS files could still no be loaded (HTTP404 returned).
Wish
It would be nice if it was possible for Pode to support relative paths. So that one does NOT have to be at the correct path when invoking the PS1 file containing Server definitions and so forth.
I hope I made myself understandable. Let me know what you think.
Thank you.
Quick helper function of redirect
to ease URL redirection from routes. It should support both 301
and 302
redirect statuses:
# sends 302
route get '/redirect' {
redirect 'https://google.com'
}
# sends 301
route get '/moved' {
redirect -moved 'https://google.com'
}
Only a small enhancement, but set up some [Alias()]
attributes on the Server
function's parameters.
Ie, -Name
could be -n
using [Alias('n')]
Hi Matthew,
When running Pode i have noticed the CPU usage in idle is arround 40% in my test environments.
Do you know if it is a .Net "feature" on Windows? (Both 2012 R2)
Just a nice to have, but this is to rename internal occurrences of $WebSession
to $WebEvent
, as it's becoming confusing have $WebSession.Session
- that and it's more of an "event" than a "session".
On setting up the file monitoring, the web server was configured to run in its own isolated runspace. SMTP and TCP servers were missed, meaning ctrl-c doesn't work.
This issue is to setup the SMTP/TCP servers to also run in their own isolated runspaces.
Similar to tools like Electron, give Pode the ability to host a web server (against localhost), and be able to display it via a GUI Desktop Application.
The Desktop App will be displayed using WPF, and host a WebBrowser within the Window.
ie:
Server {
listen 127.0.0.1:8080 http
gui 'Desktop App' @{
'Icon' = 'path/to/icon.ico';
'State' = 'Maximized';
}
}
Ability for Pode to re-use or create its own self-signed certificates when being used over HTTPS - this will be inbuilt logic to handle certs in the WebServer
, using the example web-pages-https.ps1
from @ssugar.
Note: for now this will only be for Windows environments, and not PS-Core/Linux environments. Maybe it's possible to use OpenSSL instead?
Before checking if a cert exists, check to ensure a cert isn't already bound to the IP:Port binding using netsh http show sslcert
- if there is, skip the other steps.
A new parameter on Server
called something similar to -SSLName
, which is the certificate name/domain to look for existing certs at Cert:/LocalMachine/My
or to create a new self-signed cert. (If no name supplied, use some default name like "Pode SSL Certificate").
Also another parameter for -NoSelfSignedCert
(or similar), so that if no cert is bound or can be found, then disable creating a self-signed cert and throw an error instead.
If no binding, and no cert, then create a self-signed cert using New-SelfSignedCertificate
.
Then attach the new/existing cert using netsh http add sslcert
to bind the cert to the IP:Port config.
If any middleware or route logic throws an error currently, then a 200 response is returned. This issue is to change that so a 500 is returned instead, using status 500
.
For middleware this can be done in the Invoke-PodeMiddleware
function, within the catch
of the try-catch
. For routes this could be done within the catch
of the try-catch
in the WebServer
- around line 156.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.