diff --git a/README.md b/README.md index e984b10127053adad219a8232fe4308b3e0fe126..4ec4a684930f31d9cb2b0b5b5a6bf1c49657cbc4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# REST API CLIENT +# Bash REST API client This is a bash solution to script REST API calls. @@ -12,16 +12,21 @@ The specialties of this component are * The response can be stored ... and reimported later. After import you can use the http.get* functions to fetch results from the former request. * Caching support for GET requests with a given TTL: if you repeat a request to the same url and ttl was not reached yet then you continue with the cached result -Source: <https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client> +👤 Author: Axel Hahn; Institute for Medical Education; University of Bern \ +📄 Source: <https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client> \ +📜 License: GNU GPL 3.0 \ +📗 Docs: <https://os-docs.iml.unibe.ch/bash-rest-api-client/> -License: GNU GPL 3 -## Requirements +```text +http.init +http.makeRequest 'http://www.example.com/' +``` -* Bash as shell (runs on Linux - or with Cygwin or other Bash implementations on MS Windows too.) -* Curl must be in the path -* optional: sha1sum (for export function with autogenerated filenames only) +This stores the response in a variable and has no output. +Now you can get its data, eg ---- - -[Installation](docs/10_Installation.md) | [Usage](docs/20_Usage.md) | [Examples](docs/30_Examples.md) \ No newline at end of file +* http.getStatuscode - This returns the Http status code +* http.getResponseHeader - print Http response header +* http.getResponseData - get data from curl +* http.getResponse - get response body diff --git a/docs/10_Installation.md b/docs/10_Installation.md deleted file mode 100644 index 1e67cab7fe0ba7a37b6c9299a00e8fc7a161bb3b..0000000000000000000000000000000000000000 --- a/docs/10_Installation.md +++ /dev/null @@ -1,19 +0,0 @@ -# Download - - -Download the archive i.e. as zip: - -https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client/-/archive/master/bash-rest-api-client-master.zip - -(see other formats at the download button on https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client) - -# OR: Git clone - -```sh -git clone https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client.git -``` - -# Copy somewhere - -Copy the file `rest-api-client.sh` anywhere you want. -If you need it in a single script then copy it to its directory. If you need it more often then copy it to a folter of your PATH environment. diff --git a/docs/10_Introduction.md b/docs/10_Introduction.md new file mode 100644 index 0000000000000000000000000000000000000000..fbdf5069a7a65e4aec9242106cd6ea16820e1c10 --- /dev/null +++ b/docs/10_Introduction.md @@ -0,0 +1,21 @@ +## Introduction + +### Description + +This is a bash solution to script REST API calls. + +The specialties of this component are + +* It was build to simplify http calls and handle http response for scripts +* After making a request the response stays is in memory. There is no output to any file to grep something or to cleanup after usage. +* Its functions feel a bit like class methods, i.e. http.getResponse to get the response body or http.getResponseHeader for the Http response header +* This component wraps curl - ist supports any http method +* works with anonymous requests and Basic Authentication +* The response can be stored ... and reimported later. After import you can use the http.get* functions to fetch results from the former request. +* Caching support for GET requests with a given TTL: if you repeat a request to the same url and ttl was not reached yet then you continue with the cached result + +### Requirements + +* Bash as shell (runs on Linux - or with Cygwin or other Bash implementations on MS Windows too.) +* Curl must be in the path +* optional: sha1sum (for export function with autogenerated filenames only) diff --git a/docs/20_Installation.md b/docs/20_Installation.md new file mode 100644 index 0000000000000000000000000000000000000000..4dc5de18f30ba8395c3213facd8092b4b9c0aec2 --- /dev/null +++ b/docs/20_Installation.md @@ -0,0 +1,26 @@ +## Installation + +### Get all files + +#### Git clone + +```sh +git clone https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client.git +``` + +#### Download + +Download the archive i.e. as zip: + +<https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client/-/archive/master/bash-rest-api-client-master.zip> + +(see other formats at the download button on <https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client>) + +#### Copy shell script somewhere + +Copy the file `rest-api-client.sh` anywhere you need it. +If you need it in a single script then copy it to its directory. If you need it more often then copy it to a folter of your PATH environment. + +### Get the script only + +`wget https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client/-/raw/master/rest-api-client.sh` diff --git a/docs/20_Usage.md b/docs/30_Usage.md similarity index 84% rename from docs/20_Usage.md rename to docs/30_Usage.md index 591344e563bf463fa7b67e9be9ad8bde33bc4fc8..d421e151b74bfe0cdd79040dcc08867c934f2f65 100644 --- a/docs/20_Usage.md +++ b/docs/30_Usage.md @@ -7,7 +7,7 @@ The "methods" start with "http" dot "[method]". You should start with *http.help* to get an overwiew. -```sh +```text # # step one: source the shell script # @@ -18,6 +18,15 @@ $ . ./rest-api-client.sh # $ http.help + +Bash REST API client v0.8 + +This is a bash solution to script REST API calls. + +Source:https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client +License: GNU GPL 3 + + INSTRUCTION: - Source the file once @@ -31,11 +40,19 @@ INSTRUCTION: Enable or disable debugging infos during processing. It is written to STDERR. - - initialize a request + setAccept ACCEPT + Set authentication with user and password for basic auth + Default: application/json + setAuth AUTH:PASSWORD - set authentication + Set authentication with user and password for basic auth + + setAuthorization TYPE TOKEN|HASH + Set authentication with Authorization header. + As TYPE you can use Basic|Bearer|Negotiate|... + 2nd param is the token or hased user+password http.setBody DATA set a body for POST/ PUT requests. @@ -57,6 +74,10 @@ INSTRUCTION: Set a relative url for a request. This requires to use http.setBaseUrl before. + http.addHeader HEADER_LINE + Add a header line to the request. + This command can be repeated multiple times. + - caching functions http.setCacheTtl SECONDS @@ -103,7 +124,7 @@ INSTRUCTION: Get the http status as string Ok|Redirect|Error http.getStatuscode - Get the http status code of a request as integer + Get the http status code of a request as 3 digit integer http.isOk Check if the http response code is a 2xx diff --git a/docs/30_Examples.md b/docs/40_Examples.md similarity index 81% rename from docs/30_Examples.md rename to docs/40_Examples.md index c0ca4d1aca570d535f2837227c01df9debbe7db0..e91c6e7c503638e2d22073beeb9dfd892b830db6 100644 --- a/docs/30_Examples.md +++ b/docs/40_Examples.md @@ -1,4 +1,4 @@ -# Preparation +## Preparation A required step is sourcing the script to make the http.* function available in the current shell. @@ -9,7 +9,7 @@ A required step is sourcing the script to make the http.* function available in Then you can follow the examples. -# A first example +## A first example This is quick introduction: we make a request and have a look to a few results. @@ -67,7 +67,7 @@ url_effective:http://www.iml.unibe.ch/ ``` -# Icinga2 API +## Icinga2 API In that example we have a config for an api access with url, user and password. The docs url is not required for functionality. It will be shown on errors. @@ -113,3 +113,32 @@ if [ $? -ne 0 ]; then fi echo "OK, ${myHost} was found." ``` + +## Authentication with JWT token + +The snippet shows how to fetch a token first that will be added to be used in all following requests. + +```sh +# ---------- settings +URL=https://www.example.com/ +USER=... +PASSWORD=... + +# ---------- init +http.init +http.setBaseUrl "${URL}" + +# ---------- 1st request: get a token +http.setAuth "${USER}:${PASSWORD}" +http.makeRequest GET "token" + +# fetch token +ACCESS_TOKEN=$(http.getResponse | jq -r .access_token) + +# Set token for authorization +http.setAuthorization "Bearer ${ACCESS_TOKEN}" + +# ---------- Requests to app or api +http.makeRequest GET "api" +http.makeRequest GET "app" +``` diff --git a/docs/_index.md b/docs/_index.md index 5541390c9c284956728423f483189877201c6c62..2300b3ad7ebfe98cdcef1677fa3e1cec39c5dc8c 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -1,23 +1,11 @@ -# Description - -This is a bash solution to script REST API calls. - -The specialties of this component are - -* It was build to simplify http calls and handle http response for scripts -* After making a request the response stays is in memory. There is no output to any file to grep something or to cleanup after usage. -* Its functions feel a bit like class methods, i.e. http.getResponse to get the response body or http.getResponseHeader for the Http response header -* This component wraps curl - ist supports any http method -* works with anonymous requests and Basic Authentication -* The response can be stored ... and reimported later. After import you can use the http.get* functions to fetch results from the former request. -* Caching support for GET requests with a given TTL: if you repeat a request to the same url and ttl was not reached yet then you continue with the cached result - -Source: https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client - -License: GNU GPL 3 - -# Requirements - -* Bash as shell (runs on Linux - or with Cygwin or other Bash implementations on MS Windows too.) -* Curl must be in the path -* optional: sha1sum (for export function with autogenerated filenames only) +<html> +<div class="hero"> + <h2>Bash REST API client</h2> + +</div> +</html> + +👤 Author: Axel Hahn; Institute for Medical Education; University of Bern \ +📄 Source: <https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client> \ +📜 License: GNU GPL 3.0 \ +📗 Docs: <https://os-docs.iml.unibe.ch/bash-rest-api-client/> diff --git a/rest-api-client.sh b/rest-api-client.sh index 2a55f363374498332234b5d9f4779e558b06240d..33dee2e0724aade1b00c128cfe192acf1e8423af 100644 --- a/rest-api-client.sh +++ b/rest-api-client.sh @@ -8,6 +8,10 @@ # - curl # - sha1sum (optional; for export functionality with AUTOFILE only) # ---------------------------------------------------------------------- +# License: GPL 3.0 +# Source: <https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client> +# Docs: <https://os-docs.iml.unibe.ch/bash-rest-api-client/> +# ---------------------------------------------------------------------- # (1) source this script # (2) enter "http.help" to get a list of available commands # ---------------------------------------------------------------------- @@ -16,14 +20,16 @@ # 2020-03-02 v0.5 axel.hahn@iml.unibe.ch a few more response check functions # 2021-01-21 v0.6 axel.hahn@iml.unibe.ch add Content-type in request header # 2022-01-11 v0.7 axel.hahn@iml.unibe.ch fixes using shellcheck +# 2024-10-09 v0.8 axel.hahn@unibe.ch add setAuthorization; customize accept header, add custom request headers # ====================================================================== - http_cfg__about="Bash REST API client v0.7" + http_cfg__about="Bash REST API client v0.8" typeset -i http_cfg__debug=0 typeset -i http_cfg__cacheTtl=0 http_cfg__cacheDir=/var/tmp/http-cache http_cfg__UA="${http_cfg__about}" http_cfg__prjurl="https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client" + http_cfg__docsurl="https://os-docs.iml.unibe.ch/bash-rest-api-client/" # --- curl meta infos to collect # see variables in man curl --write-out param @@ -97,6 +103,9 @@ EOH # grep "function http.[a-z]" $0 | sort } + # ...................................................................... + # + # Initialize the client function http.init(){ http._wd "${FUNCNAME[0]}()" which curl >/dev/null || http.quit @@ -104,7 +113,10 @@ EOH # request vars http_req__auth= - http_req__auth= + http_req__authorization= + http_req__accept="application/json" + http_req__headers=() + http_req__body= http_req__method=GET http_req__url= @@ -120,7 +132,6 @@ EOH http_curl__writeout="\\n${http_req__dataprefix}${writevar}\\n" # cache - http_req__mode=undefined http_cfg__cacheTtl=0 http_cfg__cacheFile= @@ -131,7 +142,10 @@ EOH chmod 777 ${http_cfg__cacheDir} 2>/dev/null } - # execute the request + # ...................................................................... + # + # Execute the request + # # param string optional: method; GET|POST|PUT|DELETE|... # param string optional: full url # param string optional: request body @@ -140,8 +154,7 @@ EOH # --- handle optional prams if [ $# -ne 0 ]; then - echo "$1" | grep "^[A-Z]*$" >/dev/null - if [ $? -eq 0 ]; then + if echo "$1" | grep -q "^[A-Z]*$"; then http.setMethod "$1" shift 1 fi @@ -151,21 +164,19 @@ EOH # test -z "$1" || http.setFullUrl "$1" # --- detect caching - http_req__mode=REQUEST - useCache=0 - makeRequest=1 + local useCache=0 + local makeRequest=1 if [ $http_cfg__cacheTtl -gt 0 ] && [ "${http_req__method}" = "GET" ]; then useCache=1 test -z "${http_cfg__cacheFile}" && http_cfg__cacheFile=$(http._genOutfilename "${http_cfg__cacheDir}/AUTOFILE") if [ -f "${http_cfg__cacheFile}" ]; then http.responseImport "${http_cfg__cacheFile}" - typeset -i local iAge + local iAge; typeset -i iAge iAge=$(http.getRequestAge) http._wd "INFO: Age of cache is $iAge sec - vs TTL $http_cfg__cacheTtl sec - file $http_cfg__cacheFile" if [ $iAge -gt 0 ] && [ $iAge -lt $http_cfg__cacheTtl ]; then http._wd "INFO: Using cache" makeRequest=0 - http_req__mode=CACHE else http._wd "INFO: Cache file will be updated after making the request" rm -f "${http_cfg__cacheFile}" 2>/dev/null @@ -176,29 +187,25 @@ EOH # --- make the request if [ $makeRequest -eq 1 ]; then - http._wd "${FUNCNAME[0]}($1) ${http_req__method} ${http_req__fullurl}" - http_resp__all=$( - if [ -z "${http_req__body}" ]; then - curl -k -s \ - -A "${http_cfg__UA}" \ - -w "${http_curl__writeout}" \ - -H 'Accept: application/json' \ - -H 'Content-type: application/json' \ - ${http_req__auth} \ - -i "${http_req__fullurl}" \ - -X "${http_req__method}" - else - curl -k -s \ - -A "${http_cfg__UA}" \ - -w "${http_curl__writeout}" \ - -H 'Accept: application/json' \ - -H 'Content-type: application/json' \ - ${http_req__auth} \ - -i "${http_req__fullurl}" \ - -X "${http_req__method}" \ - -d "${http_req__body}" - fi - ) || http.quit + + # build curl parameters + local curl_params=( + -k + -s + -i "${http_req__fullurl}" + -X "${http_req__method}" + -w "${http_curl__writeout}" + -A "${http_cfg__UA}" + ) + test -n "$http_req__auth" && curl_params+=( -u "$http_req__auth" ) + test -n "$http_req__authorization" && curl_params+=( -H "Authorization: $http_req__authorization" ) + test -n "$http_req__accept" && curl_params+=( -H "Accept: $http_req__accept" ) + test -n "$http_req__body" && curl_params+=( -d "${http_req__body}" ) + curl_params+=( "${http_req__headers[@]}" ) + + http._wd "${FUNCNAME[0]}($1) ${http_req__method} ${http_req__fullurl}" + http_resp__all=$( curl "${curl_params[@]}") || http.quit + http._wd "OK - Curl finished the http request ... processing data" http_resp__neutral=$(http._fetchAllAndReformat) if [ $useCache -eq 1 ]; then @@ -211,7 +218,8 @@ EOH # ...................................................................... # - # show error message with last return code and quit with this exitcode + # Show error message with last return code and quit with this exitcode + # # no params function http.quit(){ rc=$? @@ -252,7 +260,10 @@ EOH # GETTER # ====================================================================== - # get the response header or response body + # ...................................................................... + # + # Get the response header or response body + # # param string what to return; one of header|body function http._fetchResponseHeaderOrBody(){ http._wd "${FUNCNAME[0]}($1)" @@ -274,6 +285,10 @@ EOH fi done } + + # ...................................................................... + # + # Get the response data function http._fetchResponseData(){ http._wd "${FUNCNAME[0]}()" echo "${http_resp__all}" | sed "s#${http_req__dataprefix}#\n${http_req__dataprefix}#" | grep "${http_req__dataprefix}" | tail -1 | cut -f 2- -d "|" | sed "s#|#\n#g" | grep -v "${http_req__dataprefix}" | while read -r line; do @@ -281,6 +296,9 @@ EOH done } + # ...................................................................... + # + # Generate the dump with request and response function http._fetchAllAndReformat(){ http._wd "${FUNCNAME[0]}()" IFS='' @@ -307,6 +325,11 @@ EOH echo $line END } + # ...................................................................... + # + # Get a section from dump data + # + # param string what to return; one of HEADER|DATA|BODY function http._getFilteredResponse(){ http._wd "${FUNCNAME[0]}($1)" echo "${http_resp__neutral}" | grep "^#_${1}_|" | cut -f 2- -d "|" @@ -314,17 +337,23 @@ EOH # ---------- PUBLIC REQUEST GETTER + # ...................................................................... + # + # Get timestamp of the response function http.getRequestTs(){ http._wd "${FUNCNAME[0]}()" http._getFilteredResponse REQUEST | grep "^timestamp" | cut -f 2 -d ":" } - # get age of the response in sec. + # ...................................................................... + # + # Get age of the response in sec. # It is especially useful after responseImport + # function http.getRequestAge(){ http._wd "${FUNCNAME[0]}()" - typeset -i local iAge - typeset -i local iTs + local iAge; typeset -i iAge + local iTs; typeset -i iTs iTs=$( http.getRequestTs ) iAge=$( date +%s )-${iTs} echo "$iAge" @@ -332,29 +361,41 @@ EOH # ---------- PUBLIC RESPONSE GETTER - # get response body + # ...................................................................... + # + # Get response body function http.getResponse(){ http._wd "${FUNCNAME[0]}()" http._getFilteredResponse BODY } - # get curl data of this request with status, transferred bytes, speed, ... + + # ...................................................................... + # + # Get curl data of this request with status, transferred bytes, speed, ... function http.getResponseData(){ http._wd "${FUNCNAME[0]}()" http._getFilteredResponse DATA } - # get response header + + # ...................................................................... + # + # Get response header function http.getResponseHeader(){ http._wd "${FUNCNAME[0]}()" http._getFilteredResponse HEADER } - # get raw response (not available after import) + # ...................................................................... + # + # Get raw response (not available after import) function http.getResponseRaw(){ http._wd "${FUNCNAME[0]}()" echo "${http_resp__all}" } - # get Http status as string OK|Redirect|Error + # ...................................................................... + # + # Get Http status as string OK|Redirect|Error function http.getStatus(){ http._wd "${FUNCNAME[0]}($1)" http.isOk >/dev/null && echo OK @@ -362,44 +403,61 @@ EOH http.isError >/dev/null && echo Error } - # get Http status code of the request as 3 digit number + # ...................................................................... + # + # Get Http status code of the request as 3 digit number function http.getStatuscode(){ http._wd "${FUNCNAME[0]}()" http.getResponseData | grep "^http_code:" | cut -f 2 -d ":" } - # was response a 2xx status code? + # ...................................................................... + # + # Check: was response a 2xx status code? # output is a statuscode if it matches ... or empty # Additionally you can verify the return code + # # $? -eq 0 means YES # $? -ne 0 means NO function http.isOk(){ http._wd "${FUNCNAME[0]}()" http.getStatuscode | grep '2[0-9][0-9]' } - # was the repsonse a redirect? + + # ...................................................................... + # + # Was the repsonse a redirect? function http.isRedirect(){ http._wd "${FUNCNAME[0]}()" http.getStatuscode | grep '3[0-9][0-9]' } - # was the repsonse a client error (4xx or 5xx) + # ...................................................................... + # + # Was the repsonse a client error (4xx or 5xx) function http.isError(){ http._wd "${FUNCNAME[0]}()" http.getStatuscode | grep '[45][0-9][0-9]' } - # was the repsonse a client error (4xx) + + # ...................................................................... + # + # Was the repsonse a client error (4xx) function http.isClientError(){ http._wd "${FUNCNAME[0]}()" http.getStatuscode | grep '4[0-9][0-9]' } - # was the repsonse a client error (5xx) + + # ...................................................................... + # + # Was the repsonse a client error (5xx) function http.isServerError(){ http._wd "${FUNCNAME[0]}()" http.getStatuscode | grep '5[0-9][0-9]' } - + # ...................................................................... + # # dump information about request and response function http.dump(){ http._wd "${FUNCNAME[0]}()" @@ -410,13 +468,15 @@ EOH # Import/ Export # ====================================================================== + # ...................................................................... + # # helper to replace "AUTOFILE" with something uniq using full url + # # param string import or export filename function http._genOutfilename(){ http._wd "${FUNCNAME[0]}($1)" - echo "$1" | grep "AUTOFILE" >/dev/null - if [ $? -ne 0 ]; then - echo $1 + if echo "$1" | grep -q "AUTOFILE"; then + echo "$1" else local sum sum=$(echo ${http_req__fullurl} | sha1sum ) @@ -426,7 +486,8 @@ EOH fi } - + # ...................................................................... + # # export response to a file # param string optional: custom filename function http.responseExport(){ @@ -441,14 +502,17 @@ EOH fi } - # import a former response from a file + # ...................................................................... + # + # Import a former response from a file + # + # param string filename with stored response function http.responseImport(){ http._wd "${FUNCNAME[0]}($1)" local infile infile=$(http._genOutfilename "$1") if [ -r "${infile}" ]; then - grep "^#_META_|about:$http_cfg__about" "${infile}" >/dev/null - if [ $? -eq 0 ]; then + if grep -q "^#_META_|about:$http_cfg__about" "${infile}"; then http_resp__neutral=$(cat "${infile}") else echo "ERROR: Ooops [${infile}] does not seem to be an export dump." @@ -459,17 +523,20 @@ EOH http.quit fi } - # delete an exported file; this is especially useful if you use + + # ...................................................................... + # + # Delete an exported file; this is especially useful if you use # AUTOFILE functionality + # + # param string filename with stored response function http.responseDelete(){ http._wd "${FUNCNAME[0]}($1)" local infile infile=$(http._genOutfilename "$1") if [ -r "${infile}" ]; then - grep "^#_META_|about:$http_cfg__about" "${infile}" >/dev/null - if [ $? -eq 0 ]; then - rm -f "${infile}" - if [ $? -eq 0 ]; then + if grep -q "^#_META_|about:$http_cfg__about" "${infile}"; then + if rm -f "${infile}"; then http._wd "OK, ${infile} was deleted." else http._wd "ERROR: unable to delete existing ${infile}. Check permissions." @@ -486,31 +553,85 @@ EOH # SETTER # ====================================================================== - # set authentication + # ...................................................................... + # + # Add a line to the request header + # + # param string line to add, eg "Connection: keep-alive" + function http.addHeader(){ + http._wd "${FUNCNAME[0]}($1)" + http_req__headers+=( -H "$1") + } + + # ...................................................................... + # + # set Accept request header and override default + # + # param string accept header value, eg text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 + function http.setAccept(){ + http._wd "${FUNCNAME[0]}($1)" + if [ -z "$1" ]; then + http_req__accept= + else + http_req__accept="$1" + fi + } + + # ...................................................................... + # + # Set basic authentication + # # param string USER:PASSWORD function http.setAuth(){ http._wd "${FUNCNAME[0]}($1)" if [ -z "$1" ]; then http_req__auth= else - http_req__auth="-u $1" + http_req__auth="$1" + fi + } + + # ...................................................................... + # + # Set authentication via Athorization header + # + # param string type, eg. Basic|Bearer|Negotiate + # param string token or encoded user + password + function http.setAuthorization(){ + http._wd "${FUNCNAME[0]}($1 $2)" + if [ -z "$1" ]; then + http_req__authorization= + else + http_req__authorization="${1} ${2}" fi } - # set body to send for PUTs and POSTs + + # ...................................................................... + # + # Set body to send for PUTs and POSTs + # # param string body function http.setBody(){ http._wd "${FUNCNAME[0]}($1)" http_req__body=$1 } - # set a base url of an API + + # ...................................................................... + # + # Set a base url of an API # Remark: Then use http.setUrl to complet the url to request + # # param string url function http.setBaseUrl(){ http._wd "${FUNCNAME[0]}($1)" http_req__baseurl=$1 http.setFullUrl "" } + + # ...................................................................... + # # Enable or disable debug mode + # # param integer 0|1 function http.setDebug(){ http._wd "${FUNCNAME[0]}($1)" @@ -521,14 +642,20 @@ EOH http_req__docs=$1 } - # set the method to use; GET|POST|PUT|DELETE + # ...................................................................... + # + # Set the method to use; GET|POST|PUT|DELETE|... + # # param string name of method function http.setMethod(){ http._wd "${FUNCNAME[0]}($1)" http_req__method=$1 } - # set a full url to request + # ...................................................................... + # + # Set a full url to request + # # param string optional: url function http.setFullUrl(){ http._wd "${FUNCNAME[0]}($1)" @@ -538,7 +665,11 @@ EOH http_req__fullurl=$1 fi } - # complete the base url + + # ...................................................................... + # + # Complete the base url + # # param string url part behind base url function http.setUrl(){ http._wd "${FUNCNAME[0]}($1)" @@ -548,16 +679,29 @@ EOH # ----- caching + # ...................................................................... + # + # Set cache ttl in seconds + # + # param integer ttl in seconds function http.setCacheTtl(){ http._wd "${FUNCNAME[0]}($1)" http_cfg__cacheTtl=$1 } + # ...................................................................... + # + # Set cache file + # + # param string filename function http.setCacheFile(){ http._wd "${FUNCNAME[0]}($1)" http_cfg__cacheFile="$1" } + # ...................................................................... + # + # Flush the cache function http.flushCache(){ http._wd "${FUNCNAME[0]}($1)" rm -f ${http_cfg__cacheDir}/* @@ -574,7 +718,8 @@ $http_cfg__about This is a bash solution to script REST API calls. -Source:$http_cfg__prjurl +Source: <$http_cfg__prjurl> +Docs: <$http_cfg__docsurl> License: GNU GPL 3 @@ -593,8 +738,17 @@ INSTRUCTION: - initialize a request + setAccept ACCEPT + Set authentication with user and password for basic auth + Default: $http_req__accept + setAuth AUTH:PASSWORD - set authentication + Set authentication with user and password for basic auth + + setAuthorization TYPE TOKEN|HASH + Set authentication with Authorization header. + As TYPE you can use Basic|Bearer|Negotiate|... + 2nd param is the token or hased user+password http.setBody DATA set a body for POST/ PUT requests. @@ -616,6 +770,10 @@ INSTRUCTION: Set a relative url for a request. This requires to use http.setBaseUrl before. + http.addHeader HEADER_LINE + Add a header line to the request. + This command can be repeated multiple times. + - caching functions http.setCacheTtl SECONDS diff --git a/tests/color.class.sh b/tests/color.class.sh new file mode 100644 index 0000000000000000000000000000000000000000..362078205343127eac90576b64e96e9aca4cd3e9 --- /dev/null +++ b/tests/color.class.sh @@ -0,0 +1,601 @@ +#!/bin/bash +# ====================================================================== +# +# COLORS +# +# a few shell functions for colored output +# +# ---------------------------------------------------------------------- +# License: GPL 3.0 +# Source: <https://github.com/axelhahn/bash_colorfunctions> +# Docs: <https://www.axel-hahn.de/docs/bash_colorfunctions/> +# ---------------------------------------------------------------------- +# 2023-08-09 ahahn 0.1 initial lines +# 2023-08-09 ahahn 0.2 hide output of regex test with grep +# 2023-08-13 ahahn 0.3 introduce of color presets with foreground and background +# 2023-08-13 ahahn 0.4 list presets, debug, count of colors +# 2023-08-13 ahahn 0.5 support of RGB hex code +# 2023-08-14 ahahn 0.6 fix setting fg and bg as RGB hex code +# 2023-08-14 ahahn 0.7 remove color.ansi; respect NO_COLOR=1 +# 2023-08-16 ahahn 0.8 add function color.preset +# ====================================================================== + +_VERSION=0.8 +typeset -i COLOR_DEBUG; COLOR_DEBUG=0 + +# ---------------------------------------------------------------------- +# CONSTANTS +# ---------------------------------------------------------------------- + +declare -A BGCOLOR_CODE +declare -A COLOR_CODE + +# background colors +BGCOLOR_CODE[black]="40" +BGCOLOR_CODE[red]="41" +BGCOLOR_CODE[green]="42" +BGCOLOR_CODE[brown]="43" +BGCOLOR_CODE[blue]="44" +BGCOLOR_CODE[purple]="45" +BGCOLOR_CODE[cyan]="46" +BGCOLOR_CODE[lightgray]="47" +BGCOLOR_CODE[darkgray]="1;40" +BGCOLOR_CODE[lightred]="1;41" +BGCOLOR_CODE[lightgreen]="1;42" +BGCOLOR_CODE[yellow]="1;43" +BGCOLOR_CODE[lightblue]="1;44" +BGCOLOR_CODE[lightpurple]="1;45" +BGCOLOR_CODE[lightcyan]="1;46" +BGCOLOR_CODE[white]="1;47" + +# foreground colors +COLOR_CODE[black]="30" +COLOR_CODE[red]="31" +COLOR_CODE[green]="32" +COLOR_CODE[brown]="33" +COLOR_CODE[blue]="34" +COLOR_CODE[purple]="35" +COLOR_CODE[cyan]="36" +COLOR_CODE[lightgray]="37" +COLOR_CODE[darkgray]="1;30" +COLOR_CODE[lightred]="1;31" +COLOR_CODE[lightgreen]="1;32" +COLOR_CODE[yellow]="1;33" +COLOR_CODE[lightblue]="1;34" +COLOR_CODE[lightpurple]="1;35" +COLOR_CODE[lightcyan]="1;36" +COLOR_CODE[white]="1;37" + +# custom presets as array of foreground and background color +# +# +--- the label is part of the variable +# | +# v +# COLOR_PRESET_error=("white" "red") +# COLOR_PRESET_ok=("white" "green") + +# ---------------------------------------------------------------------- +# PRIVATE FUNCTIONS +# ---------------------------------------------------------------------- + +# write debug output - if debugging is enabled +# Its output is written to STDERR +# param string text to show +function color.__wd(){ + test "$COLOR_DEBUG" = "1" && >&2 echo "DEBUG: $*" +} + +# test, if given value is a known color name +# param string colorname to test +function color.__iscolorname(){ + test -n "${COLOR_CODE[$1]}" && return 0 + return 1 +} + +# test, if given value is a value 0..7 +# param string color to test +function color.__iscolorcode(){ + test "$1" = "0" && return 0 + test "$1" = "1" && return 0 + test "$1" = "2" && return 0 + test "$1" = "3" && return 0 + test "$1" = "4" && return 0 + test "$1" = "5" && return 0 + test "$1" = "6" && return 0 + test "$1" = "7" && return 0 + return 1 +} + +# test, if given value is an ansi code +# param string color to test +function color.__iscolorvalue(){ + if grep -E "^([01];|)[34][0-7]$" >/dev/null <<< "$1" ; then + return 0 + fi + return 1 +} + +# test, if given value is an rgb hexcode eg. #80a0f0 +# param string color to test +function color.__isrgbhex(){ + if grep -iE "^#[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]$" >/dev/null <<< "$1" ; then + return 0 + fi + return 1 +} + +# convert rgb hex code eg. #80a0f0 to 3 decimal values +# output us a string with space separated values for red, green, blue +# param string color as "#RRGGBB" to convert +function color.__getrgb(){ + local _r + local _g + local _b + if color.__isrgbhex "$1"; then + _r=$( cut -c 2,3 <<< "$1" ) + _g=$( cut -c 4,5 <<< "$1" ) + _b=$( cut -c 6,7 <<< "$1" ) + echo "$((16#$_r)) $((16#$_g)) $((16#$_b))" + fi +} + +# test, if given value is a color that can be one of +# - colorname +# - value 0..7 +# - ansi code +# param string color to test +function color.__isacolor(){ + if color.__iscolorname "$1"; then return 0; fi + if color.__iscolorcode "$1"; then return 0; fi + if color.__iscolorvalue "$1"; then return 0; fi + if color.__isrgbhex "$1"; then return 0; fi + color.__wd "$FUNCNAME is acolor: $1 --> No" + return 1 +} + +# test, if given value is an existing preset +# param string color to test +function color.__isapreset(){ + local _colorset + eval "_colorset=\$COLOR_PRESET_${1}" + test -n "$_colorset" && return 0 + return 1 +} + +# respect NO_COLOR=1 +# return 1 if colors are allowed to be used. +function color.__usecolor(){ + test "$NO_COLOR" = "1" && return 1 + return 0 +} + +# set foreground or background +# param string color as +# - basic color 0..7 OR +# - color name eg. "black" OR +# - a valid color value eg. "1;30" OR +# - a hex code eg. "#10404f" +# param integer what to set; '3' for for foreground or '4' for background colors +function color.__fgorbg(){ + local _color="$1" + local _prefix="$2" + color.__wd "$FUNCNAME $1 $2" + if color.__iscolorname "${_color}"; then + color.__wd "yep, ${_color} is a color name." + test "$_prefix" = "3" && color.set "${COLOR_CODE[${_color}]}" + test "$_prefix" = "4" && color.set "${BGCOLOR_CODE[${_color}]}" + else + if color.__iscolorcode "${_color}"; then + color.__wd "yep, ${_color} is a color code." + else + if color.__iscolorvalue "${_color}"; then + color.__wd "yep, ${_color} is a color value." + color.set "${_color}" + else + if color.__isrgbhex "${_color}"; then + local _r + local _g + local _b + read -r _r _g _b <<< $( color.__getrgb "${_color}" ) + color.set "${_prefix}8;2;$_r;$_g;$_b" + else + >&2 echo "ERROR: color '${_color}' is not a name nor a value between 0..7 nor a valid color value nor RGB." + fi + fi + fi + fi +} + +# ---------------------------------------------------------------------- +# FUNCTIONS :: helpers +# ---------------------------------------------------------------------- + +# get count of colors in the current terminal +function color.count(){ + tput colors +} + +# enable debug flag +function color.debugon(){ + COLOR_DEBUG=1 + color.__wd "$FUNCNAME - debugging is enabled now" +} + +# disable debug flag +function color.debugoff(){ + color.__wd "$FUNCNAME - disabling debugging now" + COLOR_DEBUG=0 +} + +# show debugging status +function color.debugstatus(){ + echo -n "INFO: color.debug - debugging is " + if [ $COLOR_DEBUG -eq 0 ]; then + echo "DISABLED" + else + echo "ENABLED" + fi +} + +# show help +function color.help(){ + local _self; _self='[path]/color.class.sh' + color.reset + local _debug=$COLOR_DEBUG + COLOR_DEBUG=0 + + echo "_______________________________________________________________________________" + echo + color.echo "red" " ### ### # ### ####" + color.echo "yellow" " # # # # # # # #" + color.echo "white" " # # # # # # ####" + color.echo "yellow" " # # # # # # # #" + color.echo "red" " ### ### ##### ### # #" + echo "_________________________________________________________________________/ v$_VERSION" + echo + + sed "s#^ ##g" << EOH + HELP: + 'color' is a class like component to simplify the handling of ansi colors and keeps + the color settings readable. A set NO_COLOR=1 will be respected. + + Author: Axel Hahn + License: GNU GPL 3.0 + Source: <https://github.com/axelhahn/bash_colorfunctions> + Docs: <https://www.axel-hahn.de/docs/bash_colorfunctions/> + + + FUNCTIONS: + + ---------- Information: + + color.help this help + color.list show a table with valid color names + color.presets show a table with defined custom presets + + color.count get count of colors in the current terminal + + color.debugon enable debugging + color.debugoff disable debugging + color.debugstatus show debugstatus + + ---------- Colored output: + + color.bg COLOR (COLOR2) + set a background color; a 2nd parameter is optional to set + a foreground color too + color.fg COLOR (COLOR2) + set a foreground color; a 2nd parameter is optional to set + a background color too + color.preset PRESET + Apply the color set of foreground and background of a given + preset name. + color.echo COLOR|PRESET (COLOR2) TEXT + write a colored text with carriage return and reset colors + The 1st param must be a COLOR(code/ name) for the + foreground or a label of a preset. + The 2nd CAN be a color for the background, but can be + skipped. + Everything behind is text for the output. + color.print COLOR|PRESET (COLOR2) TEXT + see color.echo - the same but without carriage return. + color.reset reset colors + color.set RAWCOLOR (RAWCOLOR2 (... RAWCOLOR_N)) + set ansi colors; it can handle multiple color values + + + ---------- Other: + + color.blink start blinking text + color.bold start bold text + color.invert start inverted text + color.underline start underline text + + VALUES: + COLOR a color; it can be... + - a color keyword, eg black, blue, red, ... for all + known values run 'color.list' + - a value 0..7 to set basic colors 30..37 (or 40..47) + - an ansi color value eg. "30" or "1;42" + - RGB hexcode with '#' as prefix followed by 2 digit + hexcode for red, green and blue eg. "#10404f" + (like css rgb color codes) + PRESET Name of a custom preset; see DEFINE PRESETS below. + RAWCOLOR an ansi color value eg. "30" (black foreground) or + "1;42" (lightgreen background) + + + DEFINE PRESETS: + A shortcut for a combination of foreground + background color. The label + is part of a bash variable with the prefix 'COLOR_PRESET_'. + The value is a bash array with 2 colors for foreground and background. + See the value description for COLOR above. + + SYNTAX: + COLOR_PRESET_<LABEL>=(<FOREGROUND> <BACKGROUND>) + + To see all defined presets use 'color.presets' + + + EXAMPLES: + First you need to source the file $_self. + . $_self + + (1) + Show output of the command 'ls -l' in blue + color.fg "blue" + ls -l + color.reset + + (2) + show a red error message + color.echo "red" "ERROR: Something went wrong." + + (3) + Use a custom preset: + COLOR_PRESET_error=("white" "red") + color.echo "error" "ERROR: Something went wrong." + + This defines a preset named "error". "white" is a colorname + for the foreground color, "red" ist the background. + +EOH + + if [ -n "$NO_COLOR" ]; then + echo -n "INFO: NO_COLOR=$NO_COLOR was set. The coloring functionality is " + if ! color.__usecolor; then + echo "DISBALED." + else + echo "ENABLED (must be 1 to disable)." + fi + echo + else + echo "INFO: NO_COLOR will be respected - but it is not set." + fi + + COLOR_DEBUG=$_debug +} + +# a little helper: show colors and the color codes +function color.list(){ + color.reset + local _debug=$COLOR_DEBUG + COLOR_DEBUG=0 + + echo + echo "List of colors:" + echo + + echo "--------------------------------------------------" + echo "color | foreground | background" + echo "--------------------------------------------------" + for i in "${!COLOR_CODE[@]}" + do + printf "%-15s %4s " $i ${COLOR_CODE[$i]} + color.set "${COLOR_CODE[$i]}" + color.set "40" + printf " Test " + + color.set "1;47" + color.set "${COLOR_CODE[$i]}" + printf " Test " + color.reset + + printf " %5s " ${BGCOLOR_CODE[$i]} + color.set ${BGCOLOR_CODE[$i]} + printf " Test " + color.reset + echo + + done | sort + color.reset + echo "--------------------------------------------------" + echo + COLOR_DEBUG=$_debug +} + +# little helper: sow defined presets and its preview +function color.presets(){ + local _label + local _value + local _colorvar + local _fg + local _bg + + color.reset + local _debug=$COLOR_DEBUG + COLOR_DEBUG=0 + + if ! set | grep "^COLOR_PRESET_.*=(" >/dev/null; then + echo "INFO: No preset was defined yet." + echo "To set one define shell variables with an array of 2 colors:" + echo " COLOR_PRESET_<LABEL>=(<FOREGROUND> <BACKGROUND>)" + echo "For more help call 'color.help' or see the docs." + else + echo + echo "List of presets:" + echo + echo "---------------------------------------------------------------------" + echo "label | foreground | background | example" + echo "---------------------------------------------------------------------" + + set | grep "^COLOR_PRESET_.*=(" | while read -r line + do + _label=$( cut -f 1 -d '=' <<< "$line" | cut -f 3- -d '_') + _example=$( color.print "$_label" "example for peset '$_label'" ) + _colorvar="COLOR_PRESET_${_label}" + eval "_fg=\${$_colorvar[0]}" + eval "_bg=\${$_colorvar[1]}" + + printf "%-10s | %-12s | %-12s | %-50s\n" "$_label" "${_fg}" "${_bg}" "$_example" + done + echo "---------------------------------------------------------------------" + echo + fi + COLOR_DEBUG=$_debug +} +# ---------------------------------------------------------------------- +# FUNCTIONS :: set color +# ---------------------------------------------------------------------- + +# set background color +# param string backround color 0..7 OR color name eg "black" or a valid color value eg "1;30" +# param string optional: foreground color +function color.bg(){ + color.__wd "$FUNCNAME $1" + color.__fgorbg "$1" 4 + test -n "$2" && color.fg "$2" +} + +# get a color of a preset +# param string name of preset +# param integer array index; 0= foreground; 1= background +function color.__getpresetcolor(){ + local _label=$1 + local _index=$2 + local _colorvar + _colorvar="COLOR_PRESET_${_label}" + eval "echo \${$_colorvar[$_index]}" +} + +# set foreground color +# param string foreground color 0..7 OR color name eg "black" or a valid color value eg "1;30" +# param string optional: background color +function color.fg(){ + color.__wd "$FUNCNAME $1" + color.__fgorbg "$1" 3 + test -n "$2" && color.bg "$2" +} + + +# set colors of a preset +# param string label of a preet +function color.preset(){ + if color.__isapreset "$1"; then + local _colorvar + local _colfg=$( color.__getpresetcolor "$1" 0) + local _colbg=$( color.__getpresetcolor "$1" 1) + color.reset + test -n "$_colfg" && color.__fgorbg "$_colfg" 3 + test -n "$_colbg" && color.__fgorbg "$_colbg" 4 + else + >&2 echo "ERROR: this value is not a valid preset: $1. See 'color.presets' to see current presets." + fi +} + +# ---------------------------------------------------------------------- + +# reset all colors to terminal default +function color.reset(){ + color.__wd "$FUNCNAME" + color.set "0" +} + +# start bold text +function color.bold(){ + color.__wd "$FUNCNAME" + color.set "1" +} + +# start underline text +function color.underline(){ + color.__wd "$FUNCNAME" + color.set "4" +} + +# start blinking text +function color.blink(){ + color.__wd "$FUNCNAME" + color.set "5" +} + +# start inverted text +function color.invert(){ + color.__wd "$FUNCNAME" + color.set "7" +} + +# ---------------------------------------------------------------------- + +# write ansicode to set color combination +# param string color 1 as ansi value +# param string color N as ansi value +function color.set(){ + local _out= + if color.__usecolor; then + for mycolor in $* + do + color.__wd "$FUNCNAME: processing color value '${mycolor}'" + _out+="${mycolor}" + done + color.__wd "$FUNCNAME: output is '\e[${_out}m'" + printf "\e[${_out}m" + else + color.__wd "$FUNCNAME: skipping - coloring is disabled." + fi +} + +# ---------------------------------------------------------------------- +# FUNCTIONS :: print +# ---------------------------------------------------------------------- + +# show a colored text WITH carriage return +# param string foreground color as code / name / value +# param string optional: background color as code / name / value +# param string text to print +function color.echo(){ + color.__wd "$FUNCNAME $*" + local _param1="$1" + local _param2="$2" + shift 1 + shift 1 + color.print "$_param1" "$_param2" "$*" + echo +} + +# show a colored text without carriage return +# param string foreground color as code / name / value or preset +# param string optional: background color as code / name / value +# param string text to print +function color.print(){ + color.__wd "$FUNCNAME $*" + if color.__isacolor "$1"; then + if color.__isacolor "$2"; then + color.fg "$1" "$2" + shift 1 + shift 1 + else + color.fg "$1" + shift 1 + fi + elif color.__isapreset "$1"; then + color.preset "$1" + shift 1 + else + >&2 echo -n "ERROR: Wrong color values detected. Command was: colors.print $*" + fi + echo -n "$*" + color.reset +} + +# ====================================================================== diff --git a/tests/example_01_simple_get.sh b/tests/example_01_simple_get.sh new file mode 100755 index 0000000000000000000000000000000000000000..4943e6c5decf841b1439ba861459766a2711fbb8 --- /dev/null +++ b/tests/example_01_simple_get.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# ====================================================================== +# +# REST API CLIENT +# +# Example 1 :: Simple GET request +# This is an introduction, how it works. +# +# ====================================================================== + +cd "$( dirname "$0" )" || exit + +# shellcheck source=../rest-api-client.sh +. ../rest-api-client.sh + +# shellcheck source=color.class.sh +. color.class.sh + +sURL="http://www.iml.unibe.ch/" + +# ---------------------------------------------------------------------- +# FUNCTIONS +# ---------------------------------------------------------------------- + +function wait4Return(){ + + local _sleep + _sleep=${1:-10} # default 10 + echo + echo + color.print "cyan" "Press return to continue or wait $_sleep seconds ... " + read -r -s -t $_sleep dummy + echo + echo + echo +} + + +# ---------------------------------------------------------------------- +# MAIN +# ---------------------------------------------------------------------- + +color.fg "yellow" +cat <<EOH +---------------------------------------------------------------------- + +Example 1 :: Simple GET request +This is a introduction, how it works. + +---------------------------------------------------------------------- + +EOH +color.reset + + +echo "We need to initialize the client with 'http.init' before each new request." +echo +color.echo "green" "> http.init" +http.init + +wait4Return 10 + +echo "Let's make a GET request to our example url $sURL." +echo +color.echo "green" "> http.makeRequest GET '$sURL'" +http.makeRequest GET "$sURL" +echo "Done." +echo "Here is no output." +echo "Now we can read information from the response." + +wait4Return 10 + +color.echo "green" "> http.getStatuscode - This returns the Http status code" +http.getStatuscode +echo +sleep 1 + +color.echo "green" "> http.getStatus - You get the Status as string OK|Redirect|Error" +http.getStatus +echo +sleep 1 + +color.echo "green" "> http.getResponseHeader - print Http response header" +http.getResponseHeader +echo +sleep 1 + +color.echo "green" "> http.getResponseData - get data from curl" +http.getResponseData +echo +sleep 1 + +color.echo "green" "> http.getResponse - get response body" +http.getResponse +echo +sleep 1 + +echo +echo "That's all for the moment. Bye." + +# ---------------------------------------------------------------------- \ No newline at end of file