Repository: icyleaf/halite
Branch: master
Commit: 07dc2f01f69b
Files: 56
Total size: 242.5 KB
Directory structure:
gitextract_d07hks70/
├── .editorconfig
├── .github/
│ ├── scripts/
│ │ └── generate_docs.sh
│ └── workflows/
│ ├── api-document.yml
│ ├── linux-ci.yml
│ └── release-version.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── shard.yml
├── spec/
│ ├── fixtures/
│ │ └── cache_file.json
│ ├── halite/
│ │ ├── client_spec.cr
│ │ ├── error_spec.cr
│ │ ├── ext/
│ │ │ ├── http_headers_encode_spec.cr
│ │ │ └── http_params_encode_spec.cr
│ │ ├── feature_spec.cr
│ │ ├── features/
│ │ │ ├── cache_spec.cr
│ │ │ └── logging_spec.cr
│ │ ├── header_link_spec.cr
│ │ ├── mime_type_spec.cr
│ │ ├── mime_types/
│ │ │ └── json_spec.cr
│ │ ├── options/
│ │ │ ├── follow_spec.cr
│ │ │ └── timeout_spec.cr
│ │ ├── options_spec.cr
│ │ ├── rate_limit_spec.cr
│ │ ├── redirector_spec.cr
│ │ ├── request_spec.cr
│ │ └── response_spec.cr
│ ├── halite_spec.cr
│ ├── spec_helper.cr
│ └── support/
│ ├── mock_server/
│ │ └── route_handler.cr
│ └── mock_server.cr
└── src/
├── halite/
│ ├── chainable.cr
│ ├── client.cr
│ ├── error.cr
│ ├── ext/
│ │ ├── file_to_json.cr
│ │ ├── http_headers_encode.cr
│ │ └── http_params_encode.cr
│ ├── feature.cr
│ ├── features/
│ │ ├── cache.cr
│ │ ├── logging/
│ │ │ ├── common.cr
│ │ │ └── json.cr
│ │ └── logging.cr
│ ├── form_data.cr
│ ├── header_link.cr
│ ├── mime_type.cr
│ ├── mime_types/
│ │ └── json.cr
│ ├── options/
│ │ ├── follow.cr
│ │ └── timeout.cr
│ ├── options.cr
│ ├── rate_limit.cr
│ ├── redirector.cr
│ ├── request.cr
│ └── response.cr
└── halite.cr
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
[*.cr]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
================================================
FILE: .github/scripts/generate_docs.sh
================================================
#!/usr/bin/env sh
DOCS_PATH="docs"
TAGS=$(git tag -l)
DEFAULT_VERSION=$(git tag -l | sort -V | tail -n 1)
DEFAULT_VERSION=$(echo $DEFAULT_VERSION | awk '{gsub(/^v/, ""); print}')
if [ -z "$DEFAULT_VERSION" ]; then
echo "Not fount default version"
exit 1
fi
# Clean up
rm -rf $DOCS_PATH
mkdir -p $DOCS_PATH
# Generate master docs
COMMIT_DATE=$(git log -1 --format=%ci)
MASTER_COMMIT_HASH=$(git rev-parse --short HEAD)
COMMIT_STATUS="[#${MASTER_COMMIT_HASH}](${GH_REF}/commit/${MASTER_COMMIT_HASH})"
sed -i -e "s/latest commit/$(echo ${COMMIT_STATUS} | sed -e "s/\//\\\\\//g") (${COMMIT_DATE})/" README.md
crystal docs --output="${DOCS_PATH}/master" --project-version="master-dev" --json-config-url="/halite/version.json"
git reset --hard
version_gt () {
test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
}
echo "{\"versions\": [" > docs/version.json
echo "{\"name\": \"master-dev\", \"url\": \"/halite/master/\", \"released\": false}" >> docs/version.json
# Generate version docs
for TAG in $(git tag -l | sort -r -V); do
NAME=$(echo $TAG | awk '{gsub(/^v/, ""); print}')
# Crystal version 1.0 complie version must great than 0.12.0.
if version_gt $NAME "0.11.0"; then
git checkout -b $NAME $TAG
echo ",{\"name\": \"$NAME\", \"url\": \"/halite/$NAME/\"}" >> docs/version.json
COMMIT_STATUS="[${TAG}](${GH_REF}/blob/master/CHANGELOG.md)"
sed -i -e "s/latest commit/$(echo ${COMMIT_STATUS} | sed -e "s/\//\\\\\//g")/" README.md
crystal docs --output="${DOCS_PATH}/${NAME}" --project-version="${NAME}" --json-config-url="/halite/version.json"
git reset --hard
git checkout master
git branch -d $NAME
fi
done
echo "]}" >> docs/version.json
echo "
Redirect to ${DEFAULT_VERSION}
" > "${DOCS_PATH}/index.html" ================================================ FILE: .github/workflows/api-document.yml ================================================ name: Deploy API documents on: push: paths-ignore: - "benchmarks/**" branches: - "master" tags: - "v*" env: DOCS_PATH: docs GH_REF: https://github.com/icyleaf/halite GH_URL: https://icyleaf.github.io/halite jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@master with: # fetch all tags and branches fetch-depth: 0 - uses: oprypin/install-crystal@v1 - name: Generate id: generate run: | chmod +x .github/scripts/generate_docs.sh ./.github/scripts/generate_docs.sh - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ${{ env.DOCS_PATH }} ================================================ FILE: .github/workflows/linux-ci.yml ================================================ name: Linux CI on: push: paths-ignore: - "benchmarks/**" branches: - "master" pull_request: branches: "*" jobs: specs: runs-on: ubuntu-latest strategy: fail-fast: false matrix: crystal: [ '1.0.0', 'latest', 'nightly' ] name: Crystal ${{ matrix.crystal }} tests steps: - uses: actions/checkout@master - uses: oprypin/install-crystal@v1 with: crystal: ${{ matrix.crystal }} - name: Install dependencies run: shards install - name: Run tests run: crystal spec --error-on-warnings --error-trace - name: Run code format check run: | if ! crystal tool format --check; then crystal tool format git diff exit 1 fi ================================================ FILE: .github/workflows/release-version.yml ================================================ name: Deploy new release on: push: tags: - "v*" jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: Release ${{ github.ref }} draft: false prerelease: false ================================================ FILE: .gitignore ================================================ docs/ lib/ bin/ logs/ .shards/ # Local test file main.cr # Libraries don't need dependency lock # Dependencies will be locked in application that uses them shard.lock .history/ cache/ ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] > List all changes before release a new version. ### Todo - [ ] Rewrite HTTP Connection - [ ] New Connection for Halite - [x] Proxy support - [ ] Reuse connection ## [0.12.1] (2021-11-04) ### Fixed - Reduce alloc too many memory. [#108](https://github.com/icyleaf/halite/pull/108) (thanks @[wolfgang371](https://github.com/wolfgang371)) ## [0.12.0] (2021-03-24) ### Fixed - Compatibility with Crystal 1.0. ## [0.11.0] (2021-02-18) > Finally, the major version was out! Happy new year! ### Changed - **[breaking changing]** Drop file logging in favor of Crystal's [Log](https://crystal-lang.org/api/0.36.1/Log.html). (removed `.logging(file: )`, use `.logging(for: )` instead) [#101](https://github.com/icyleaf/halite/pull/101) (thanks @[oprypin](https://github.com/oprypin)) - Pre-read `TZ` environment value to convert timestamp's timezone during logging output. [#102](https://github.com/icyleaf/halite/pull/102) - Crystal 0.34.x support. ## [0.10.9] (2021-02-01) ### Fixed - `timeout` fail to match argument type. [#97](https://github.com/icyleaf/halite/issues/97) (thanks @[oprypin](https://github.com/oprypin)) - Compatibility with Crystal 0.36.0. ## [0.10.8] (2020-12-22) ### Fixed - Resolve path of endpoint ending without slash. [#94](https://github.com/icyleaf/halite/issues/94) (thanks @[mipmip](https://github.com/mipmip)) ## [0.10.7] (2020-12-08) ### Fixed - Fix initial status_message. [#91](https://github.com/icyleaf/halite/issues/91) (thanks @[oprypin](https://github.com/oprypin)) ## [0.10.6] (2020-11-24) ### Fixed - Improve resolve of URI. [#88](https://github.com/icyleaf/halite/issues/88) (thanks @[oprypin](https://github.com/oprypin)) ## [0.10.5] (2020-04-15) ### Fixed - Compatibility with Crystal 0.34.0. ## [0.10.4] (2019-09-26) ### Fixed - Compatibility with Crystal 0.31.0. ## [0.10.3] (2019-08-12) ### Fixed - Compatibility with Crystal 0.30.0. ## [0.10.2] (2019-06-24) ### Fixed - Fixed Basic Auth creates bad headers in crystal 0.29.0. [#73](https://github.com/icyleaf/halite/pull/73) (thanks @[kalinon](https://github.com/kalinon)) - Fixed use one shared options in multiple instanced `Halite::Client`. [#72](https://github.com/icyleaf/halite/issues/72) (thanks @[qszhu](https://github.com/qszhu)) ## [0.10.1] (2019-05-28) ### Fixed - Fixed duplica query and backslash when reuse client. [#67](https://github.com/icyleaf/halite/pull/67), [#68](https://github.com/icyleaf/halite/issues/68) (thanks @[watzon](https://github.com/watzon)) - Fixed no effect to call `logging(true)` method in Crystal 0.28. [#69](https://github.com/icyleaf/halite/issues/69) ## [0.10.0] (2019-05-20) ### Added - Add `endpoint` chainable method, also add it as configuration option to reuse client. [#66](https://github.com/icyleaf/halite/pull/66) ## [0.9.2] (2019-05-20) ### Fixed - Compatibility with Crystal 0.28.0 ### Changed - Drop Crystal 0.25.x, 0.26.x, 0.27.x support. ## [0.9.1] (2019-01-14) > Minor typo fix (same as v0.9.0) ### Fixed - Correct version both in `shard.yml` and `version.cr`. (thanks @[matthewmcgarvey](https://github.com/matthewmcgarvey)) - Update basic auth example in `README.md`. (thanks @[matthewmcgarvey](https://github.com/matthewmcgarvey)) ## [0.9.0] (2018-12-21) > New features with performance improved. ### Added - Add streaming requests (feature to store binary data chunk by chunk) [#53](https://github.com/icyleaf/halite/pull/53) - Add `user_agent` to Chainable methods. [#55](https://github.com/icyleaf/halite/pull/55) ### Fixed - Fix overwrite the value with default headers when use `merge` or `merge!` method in `Halite::Options`. [#54](https://github.com/icyleaf/halite/pull/54) ### Changed - Remove default headers in `Halite::Options`. - Move header `User-Agent` to `Halite::Request`. - Change header `Connection` from "keep-alive" to "close" to `Halite::Request`. - Remove header `Accept`. ## [0.8.0] (2018-11-30) > Compatibility with Crystal 0.27 and serious bugfix. ### Changed - **[breaking changing]** Rename `logger` to `logging`, `with_logger` to `with_logging`. [#52](https://github.com/icyleaf/halite/pull/52) - **[breaking changing]** Remove `logging` argument in `Halite::Options.new` and `Halite::Client.new`. [#51](https://github.com/icyleaf/halite/pull/51) - **[breaking changing]** Remove `logging?` method in `Halite::Options`, use `logging` method instead. [#51](https://github.com/icyleaf/halite/pull/51) - Change `logging` behavior check if features is exists any class of superclasses is `Halite::Logging` instead of given a Bool type. - Rename prefix `X-Cache` to `X-Halite-Cache` in cache feature. ### Added - Allow `timeout` method passed single `read` or `connect` method. - Add `merge!` and `dup` methods in `Halite::Options`. [#51](https://github.com/icyleaf/halite/pull/51) ### Fixed - Fix duplice add "Content-Type" into header during request. [#50](https://github.com/icyleaf/halite/pull/50) - Fix non overwrite value of headers use `Halite::Options.merge` method. [#50](https://github.com/icyleaf/halite/pull/50) - Fix always overwrite and return merged option in a instanced class(session mode), see updated note in [Session](https://github.com/icyleaf/halite#sessions). ### Tested - Compatibility with Crystal 0.27 - Add specs with Crystal 0.25, 0.26 and 0.27 in Circle CI. ## [0.7.5] (2018-10-31) ### Changed - **[breaking changing]** Rename argument name `ssl` to `tls` in `Halite::Client`/`Halite::Options`/`Halite::Chainable`. ### Fixed - Fix new a `Halite::Client` instance with empty block return `Nil`. [#44](https://github.com/icyleaf/halite/issues/44) ## [0.7.4] (2018-10-30) ### Fixed - Fix typos in document and comments. [#43](https://github.com/icyleaf/halite/issues/43) (thanks @[GloverDonovan](https://github.com/GloverDonovan)) ## [0.7.3] (2018-10-18) ### Fixed - Fix json payloads with sub hash/array/namedtupled. [#41](https://github.com/icyleaf/halite/issues/41) (thanks @[fusillicode](https://github.com/fusillicode)) ## [0.7.2] (2018-09-14) > Minor bugfix :bug: ### Changed - **[breaking changing]** Renamed `#to_h` to `#to_flat_h` to avoid confict in `HTTP::Params` extension. [#39](https://github.com/icyleaf/halite/issues/39) ### Fixed - Fix cast from NamedTuple(work: String) to Halite::Options::Type failed with params/json/form. [#38](https://github.com/icyleaf/halite/issues/38) ## [0.7.1] (2018-09-04) ### Changed - Return empty hash for an empty named tuple. ### Fixed - Fix send cookie during requesting in session mode. (thanks @[megatux](https://github.com/megatux)) - Fix pass current options instead of instance variable. - Fix move named tuple extension to src path. ## [0.7.0] (2018-09-03) > Features support :tada: ### Changed - **[breaking changing]** Change instance `Halite::Client` with block behavior. [#33](https://github.com/icyleaf/halite/issues/33) - **[breaking changing]** Renamed argument name `adapter` to `format` in `#logger` chainable method. - Move logger into features. ### Added - Add features (aka middleware) support, you can create monitor or interceptor. [#29](https://github.com/icyleaf/halite/issues/29) - Add cache feature. [#24](https://github.com/icyleaf/halite/issues/24) - Add `#logging` in chainable method. ### Fixed - Add misisng `#request` method with headers, params, form, json, raw, ssl arguments. - Fix do not overwrite default headers with exists one by using `Halite::Options.merge`. - Fix append response to history only with redirect uri. (thanks @[j8r](https://github.com/j8r)) - Typo and correct words in README. (thanks @[megatux](https://github.com/megatux)) ## [0.6.0] (2018-08-24) > Improve performance with :see_no_evil: ### Changed - **[breaking changing]** Set `logger` to nil when instance a `Halite::Options`, it throws a `Halite::Error` exception if enable `logging`. - Change `Halite::Options` accepts argument inside. no effect for users. [#27](https://github.com/icyleaf/halite/pull/27) - Wrap all exception class into a module, better for reading document. ### Fixed - Fix always return `#` with `#full_path` if fragment not exists in `Halite::Request`. - Fix always overwrite with default headers with `#merge` in `Halite::Options` ### Tested - Compatibility with Crystal 0.26 ## [0.5.0] (2018-07-03) ### Changed - New logger system and json logger support, see [#19](https://github.com/icyleaf/halite/pull/19). - **[breaking changing]** Change verb request behavior: - `get`, `head` only accepts `#params` argument. - `post`, `put`, `delete`, `patch`, `options` accepts `#params`, `#form`, `#json` and `#raw` arguments. ### Added - Add request [#raw](https://github.com/icyleaf/halite/#raw-string) string support. [#20](https://github.com/icyleaf/halite/issues/20) (thanks @[wirrareka](https://github.com/wirrareka)) ## [0.4.0] (2018-06-27) ### Changed - **[breaking changing]** Remove `#mime_type` duplicate with `#content_type` in `Halite::Response`. - Change write log file use append mode by default, it could be change by param. - Change logger formatter to easy identify category(request/response). ### Added - Add [#links](https://github.com/icyleaf/halite/#link-headers) to `Halite::Response` to fetch link headers. - Add [#raise_for_status](https://github.com/icyleaf/halite/#raise-for-status-code) to `Halite::Response`. - Support multiple files upload. [#14](https://github.com/icyleaf/halite/issues/14) (thanks @[BenDietze](https://github.com/BenDietze)) - Add `#to_raw` to `Halite::Response` to dump a raw of response. [#15](https://github.com/icyleaf/halite/issues/15) (thanks @[BenDietze](https://github.com/BenDietze)) - Support `OPTIONS` method (crystal 0.25.0+) - Append write log to a file section to README. ### Fixed - Stripped the filename in a `multipart/form-data` body. [#16](https://github.com/icyleaf/halite/issues/16) (thanks @[BenDietze](https://github.com/BenDietze)) - Fix `#domain` in `Halite::Request` with subdomain. [#17](https://github.com/icyleaf/halite/pull/17) (thanks @[007lva](https://github.com/007lva)) - Create missing directories when use path to write log to a file. ## [0.3.2] (2018-06-19) ### Fixed Compatibility with Crystal 0.25 ## [0.3.1] (2017-12-13) ### Added - Set `Options.default_headers` to be public method. - Accept tuples options in `Options.new`. - Accept `follow`/`follow_strict` in `Options.new`. - Accept options block in `Options.new`. - Add logger during request and response (see [usage](README.md#logging)). - Alias method `Options.read_timeout` to `Options::Timeout.read`. - Alias method `Options.read_timeout=` to `Options::Timeout.read=`. - Alias method `Options.connect_timeout` to `Options::Timeout.connect`. - Alias method `Options.connect_timeout` to `Options::Timeout.connect=`. - Alias method `Options.follow=` to `Options::Timeout.follow.hops=`. - Alias method `Options.follow_strict` to `Options::Timeout.follow.strict`. - Alias method `Options.follow_strict=` to `Options::Timeout.follow.strict=`. ### Fixed - Fix store **Set-Cookies** in response and set **Cookies** in request in better way. - Fix cant not set connect/read timeout in `Options.new`. - Fix cant not overwrite default headers in `Options.new`. - Fix `Options.clear!` was not clear everything and restore default headers. ## [0.2.0] (2017-11-28) ### Changed - `HTTP::Headers#to_h` return string with each key if it contains one in array. ([commit#e057c47c](https://github.com/icyleaf/halite/commit/e057c47c4b587b27b2bae6871a1968299ce348f5)) ### Added - Add `Response#mime_type` method. - Add `Response#history` method to support full history of redirections. ([#8](https://github.com/icyleaf/halite/issues/8)) - Add `Response#parse` method that it better body parser of response with json and write custom adapter for MIME type. ([#9](https://github.com/icyleaf/halite/issues/9)) ### Fixed - Fix issue to first char of redirect uri is not slash(/). ([#11](https://github.com/icyleaf/halite/issues/11)) - Fix raise unsafe verbs in strict mode. ## [0.1.5] (2017-10-11) ### Changed - Only store cookies in Sessions shards. ([#7](https://github.com/icyleaf/halite/issues/7)) ### Added - Add `TLS/SSL` support (based on [HTTP::Client.new(uri : URI, tls = nil)](https://crystal-lang.org/api/0.23.1/HTTP/Client.html#new%28uri%3AURI%2Ctls%3Dnil%29-class-method)). - Add `UnsupportedMethodError/UnsupportedSchemeError` exceptions. ### Fixed - Timeout with redirection. ([#7](https://github.com/icyleaf/halite/issues/7)) - Compatibility with Crystal 0.24.0 (unreleased) ## [0.1.3] (2017-10-09) ### Changed - Always instance a new Options with each request in chainable methods. ### Added - Add `accept` method. ### Fixed - Fix `follow`(redirect uri) with full uri and relative path. - Fix always overwrite request headers with default values. - Fix always shard same options in any new call. (it only valid in chainable methods) ## 0.1.2 (2017-09-18) - First beta version. [Unreleased]: https://github.com/icyleaf/halite/compare/v0.12.1...HEAD [0.12.1]: https://github.com/icyleaf/halite/compare/v0.12.0...v0.12.1 [0.12.0]: https://github.com/icyleaf/halite/compare/v0.11.0...v0.12.0 [0.11.0]: https://github.com/icyleaf/halite/compare/v0.10.9...v0.11.0 [0.10.9]: https://github.com/icyleaf/halite/compare/v0.10.8...v0.10.9 [0.10.8]: https://github.com/icyleaf/halite/compare/v0.10.7...v0.10.8 [0.10.7]: https://github.com/icyleaf/halite/compare/v0.10.6...v0.10.7 [0.10.6]: https://github.com/icyleaf/halite/compare/v0.10.5...v0.10.6 [0.10.5]: https://github.com/icyleaf/halite/compare/v0.10.4...v0.10.5 [0.10.4]: https://github.com/icyleaf/halite/compare/v0.10.3...v0.10.4 [0.10.3]: https://github.com/icyleaf/halite/compare/v0.10.2...v0.10.3 [0.10.2]: https://github.com/icyleaf/halite/compare/v0.10.1...v0.10.2 [0.10.1]: https://github.com/icyleaf/halite/compare/v0.10.0...v0.10.1 [0.10.0]: https://github.com/icyleaf/halite/compare/v0.9.2...v0.10.0 [0.9.2]: https://github.com/icyleaf/halite/compare/v0.9.1...v0.9.2 [0.9.1]: https://github.com/icyleaf/halite/compare/v0.9.0...v0.9.1 [0.9.0]: https://github.com/icyleaf/halite/compare/v0.8.0...v0.9.0 [0.8.0]: https://github.com/icyleaf/halite/compare/v0.7.5...v0.8.0 [0.7.5]: https://github.com/icyleaf/halite/compare/v0.7.4...v0.7.5 [0.7.4]: https://github.com/icyleaf/halite/compare/v0.7.3...v0.7.4 [0.7.3]: https://github.com/icyleaf/halite/compare/v0.7.2...v0.7.3 [0.7.2]: https://github.com/icyleaf/halite/compare/v0.7.1...v0.7.2 [0.7.1]: https://github.com/icyleaf/halite/compare/v0.7.0...v0.7.1 [0.7.0]: https://github.com/icyleaf/halite/compare/v0.6.0...v0.7.0 [0.6.0]: https://github.com/icyleaf/halite/compare/v0.5.0...v0.6.0 [0.5.0]: https://github.com/icyleaf/halite/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/icyleaf/halite/compare/v0.3.2...v0.4.0 [0.3.2]: https://github.com/icyleaf/halite/compare/v0.3.1...v0.3.2 [0.3.1]: https://github.com/icyleaf/halite/compare/v0.2.0...v0.3.1 [0.2.0]: https://github.com/icyleaf/halite/compare/v0.1.5...v0.2.0 [0.1.5]: https://github.com/icyleaf/halite/compare/v0.1.3...v0.1.5 [0.1.3]: https://github.com/icyleaf/halite/compare/v0.1.2...v0.1.3 ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at icyleaf.cn@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2017-present icyleaf Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================  # Halite [](https://github.com/crystal-lang/crystal) [](https://github.com/icyleaf/halite/blob/master/CHANGELOG.md) [](https://github.com/icyleaf/halite/) [](https://icyleaf.github.io/halite/) [](https://github.com/icyleaf/halite/actions?query=workflow%3A%22Linux+CI%22) HTTP Requests with a chainable REST API, built-in sessions and middleware written by [Crystal](https://crystal-lang.org/). Inspired from the **awesome** Ruby's [HTTP](https://github.com/httprb/http)/[RESTClient](https://github.com/rest-client/rest-client) and Python's [requests](https://github.com/requests/requests). Build in Crystal version `>= 1.0.0`, this document valid with latest commit. ## Index - [Installation](#installation) - [Usage](#usage) - [Making Requests](#making-requests) - [Passing Parameters](#passing-parameters) - [Query string parameters](#query-string-parameters) - [Form data](#form-data) - [File uploads (via form data)](#file-uploads-via-form-data) - [JSON data](#json-data) - [Raw String](#raw-string) - [Passing advanced options](#passing-advanced-options) - [Auth](#auth) - [User Agent](#user-agent) - [Headers](#headers) - [Cookies](#cookies) - [Redirects and History](#redirects-and-history) - [Timeout](#timeout) - [HTTPS](#https) - [Response Handling](#response-handling) - [Response Content](#response-content) - [JSON Content](#json-content) - [Parsing Content](#parsing-content) - [Binary Data](#binary-data) - [Error Handling](#error-handling) - [Raise for status code](#raise-for-status-code) - [Middleware](#middleware) - [Write a simple feature](#write-a-simple-feature) - [Write a interceptor](#write-a-interceptor) - [Advanced Usage](#advanced-usage) - [Configuring](#configuring) - [Endpoint](#endpoint) - [Sessions](#sessions) - [Streaming Requests](#streaming-requests) - [Logging](#logging) - [Local Cache](#local-cache) - [Link Headers](#link-headers) ## Installation Add this to your application's `shard.yml`: ```yaml dependencies: halite: github: icyleaf/halite ``` ## Usage ```crystal require "halite" ``` ### Making Requests Make a GET request: ```crystal # Direct get url Halite.get("http://httpbin.org/get") # Support NamedTuple as query params Halite.get("http://httpbin.org/get", params: { language: "crystal", shard: "halite" }) # Also support Hash as query params Halite.get("http://httpbin.org/get", headers: { "Private-Token" => "T0k3n" }, params: { "language" => "crystal", "shard" => "halite" }) # And support chainable Halite.header(private_token: "T0k3n") .get("http://httpbin.org/get", params: { "language" => "crystal", "shard" => "halite" }) ``` See also all [chainable methods](https://icyleaf.github.io/halite/Halite/Chainable.html). Many other HTTP methods are available as well: - `get` - `head` - `post` - `put` - `delete` - `patch` - `options` ### Passing Parameters #### Query string parameters Use the `params` argument to add query string parameters to requests: ```crystal Halite.get("http://httpbin.org/get", params: { "firstname" => "Olen", "lastname" => "Rosenbaum" }) ``` #### Form data Use the `form` argument to pass data serialized as form encoded: ```crystal Halite.post("http://httpbin.org/post", form: { "firstname" => "Olen", "lastname" => "Rosenbaum" }) ``` #### File uploads (via form data) To upload files as if form data, construct the form as follows: ```crystal Halite.post("http://httpbin.org/post", form: { "username" => "Quincy", "avatar" => File.open("/Users/icyleaf/quincy_avatar.png") }) ``` It is possible to upload multiple files: ```crystal Halite.post("http://httpbin.org/post", form: { photos: [ File.open("/Users/icyleaf/photo1.png"), File.open("/Users/icyleaf/photo2.png") ], album_name: "samples" }) ``` Or pass the name with `[]`: ```crystal Halite.post("http://httpbin.org/post", form: { "photos[]" => [ File.open("/Users/icyleaf/photo1.png"), File.open("/Users/icyleaf/photo2.png") ], "album_name" => "samples" }) ``` Multiple files can also be uploaded using both ways above, it depend on web server. #### JSON data Use the `json` argument to pass data serialized as body encoded: ```crystal Halite.post("http://httpbin.org/post", json: { "firstname" => "Olen", "lastname" => "Rosenbaum" }) ``` #### Raw String Use the `raw` argument to pass raw string as body and set the `Content-Type` manually: ```crystal # Set content-type to "text/plain" by default Halite.post("http://httpbin.org/post", raw: "name=Peter+Lee&address=%23123+Happy+Ave&language=C%2B%2B") # Set content-type manually Halite.post("http://httpbin.org/post", headers: { "content-type" => "application/json" }, raw: %Q{{"name":"Peter Lee","address":"23123 Happy Ave","language":"C++"}} ) ``` ### Passing advanced options #### Auth Use the `#basic_auth` method to perform [HTTP Basic Authentication](http://tools.ietf.org/html/rfc2617) using a username and password: ```crystal Halite.basic_auth(user: "user", pass: "p@ss").get("http://httpbin.org/get") # We can pass a raw authorization header using the auth method: Halite.auth("Bearer dXNlcjpwQHNz").get("http://httpbin.org/get") ``` #### User Agent Use the `#user_agent` method to overwrite default one: ```crystal Halite.user_agent("Crystal Client").get("http://httpbin.org/user-agent") ``` #### Headers Here are two way to passing headers data: ##### 1. Use the `#headers` method ```crystal Halite.headers(private_token: "T0k3n").get("http://httpbin.org/get") # Also support Hash or NamedTuple Halite.headers({ "private_token" => "T0k3n" }).get("http://httpbin.org/get") # Or Halite.headers({ private_token: "T0k3n" }).get("http://httpbin.org/get") ``` ##### 2. Use the `headers` argument in the available request method: ```crystal Halite.get("http://httpbin.org/anything" , headers: { private_token: "T0k3n" }) Halite.post("http://httpbin.org/anything" , headers: { private_token: "T0k3n" }) ``` #### Cookies ##### Passing cookies in requests The `Halite.cookies` option can be used to configure cookies for a given request: ```crystal Halite.cookies(session_cookie: "6abaef100b77808ceb7fe26a3bcff1d0") .get("http://httpbin.org/headers") ``` ##### Get cookies in requests To obtain the cookies(cookie jar) for a given response, call the `#cookies` method: ```crystal r = Halite.get("http://httpbin.org/cookies?set?session_cookie=6abaef100b77808ceb7fe26a3bcff1d0") pp r.cookies # => #You should be redirected automatically to target URL: /cookies. If not click the link.
# => 2018-06-25 18:41:06 +08:00 | request | GET | http://httpbin.org/cookies
# => 2018-06-25 18:41:07 +08:00 | response | 200 | http://httpbin.org/cookies | application/json
# => {"cookies":{"private_token":"6abaef100b77808ceb7fe26a3bcff1d0"}}
```
All it support with [chainable methods](https://icyleaf.github.io/halite/Halite/Chainable.html) in the other examples list in [requests.Session](http://docs.python-requests.org/en/master/user/advanced/#session-objects).
Note, however, that chainable methods will not be persisted across requests, even if using a session. This example will only send the cookies or headers with the first request, but not the second:
```crystal
client = Halite::Client.new
r = client.cookies("username": "foobar").get("http://httpbin.org/cookies")
r.body # => {"cookies":{"username":"foobar"}}
r = client.get("http://httpbin.org/cookies")
r.body # => {"cookies":{}}
```
If you want to manually add cookies, headers (even features etc) to your session, use the methods start with `with_` in `Halite::Options`
to manipulate them:
```crystal
r = client.get("http://httpbin.org/cookies")
r.body # => {"cookies":{}}
client.options.with_cookie("username": "foobar")
r = client.get("http://httpbin.org/cookies")
r.body # => {"cookies":{"username":"foobar"}}
```
### Streaming Requests
Similar to [HTTP::Client](https://crystal-lang.org/api/0.36.1/HTTP/Client.html#streaming) usage with a block,
you can easily use same way, but Halite returns a `Halite::Response` object:
```crystal
r = Halite.get("http://httpbin.org/stream/5") do |response|
response.status_code # => 200
response.body_io.each_line do |line|
puts JSON.parse(line) # => {"url" => "http://httpbin.org/stream/5", "args" => {}, "headers" => {"Host" => "httpbin.org", "Connection" => "close", "User-Agent" => "Halite/0.8.0", "Accept" => "*/*", "Accept-Encoding" => "gzip, deflate"}, "id" => 0_i64}
end
end
```
> **Warning**:
>
> `body_io` is avaiabled as an `IO` and not reentrant safe. Might throws a "Nil assertion failed" exception if there is no data in the `IO`
(such like `head` requests). Calling this method multiple times causes some of the received data being lost.
>
> One more thing, use streaming requests the response will always [enable redirect](#redirects-and-history) automatically.
### Logging
Halite does not enable logging on each request and response too.
We can enable per operation logging by configuring them through the chaining API.
By default, Halite will logging all outgoing HTTP requests and their responses(without binary stream) to `STDOUT` on DEBUG level.
You can configuring the following options:
- `logging`: Instance your `Halite::Logging::Abstract`, check [Use the custom logging](#use-the-custom-logging).
- `format`: Output format, built-in `common` and `json`, you can write your own.
- `file`: Write to file with path, works with `format`.
- `filemode`: Write file mode, works with `format`, by default is `a`. (append to bottom, create it if file is not exist)
- `skip_request_body`: By default is `false`.
- `skip_response_body`: By default is `false`.
- `skip_benchmark`: Display elapsed time, by default is `false`.
- `colorize`: Enable colorize in terminal, only apply in `common` format, by default is `true`.
> **NOTE**: `format` (`file` and `filemode`) and `logging` are conflict, you can not use both.
Let's try with it:
```crystal
# Logging json request
Halite.logging
.get("http://httpbin.org/get", params: {name: "foobar"})
# => 2018-06-25 18:33:14 +08:00 | request | GET | http://httpbin.org/get?name=foobar
# => 2018-06-25 18:33:15 +08:00 | response | 200 | http://httpbin.org/get?name=foobar | 381.32ms | application/json
# => {"args":{"name":"foobar"},"headers":{"Accept":"*/*","Accept-Encoding":"gzip, deflate","Connection":"close","Host":"httpbin.org","User-Agent":"Halite/0.3.2"},"origin":"60.206.194.34","url":"http://httpbin.org/get?name=foobar"}
# Logging image request
Halite.logging
.get("http://httpbin.org/image/png")
# => 2018-06-25 18:34:15 +08:00 | request | GET | http://httpbin.org/image/png
# => 2018-06-25 18:34:15 +08:00 | response | 200 | http://httpbin.org/image/png | image/png
# Logging with options
Halite.logging(skip_request_body: true, skip_response_body: true)
.post("http://httpbin.org/get", form: {image: File.open("halite-logo.png")})
# => 2018-08-28 14:33:19 +08:00 | request | POST | http://httpbin.org/post
# => 2018-08-28 14:33:21 +08:00 | response | 200 | http://httpbin.org/post | 1.61s | application/json
```
#### JSON-formatted logging
It has JSON formatted for developer friendly logging.
```
Halite.logging(format: "json")
.get("http://httpbin.org/get", params: {name: "foobar"})
```
#### Write to a log file
```crystal
# Write plain text to a log file
Log.setup("halite.file", backend: Log::IOBackend.new(File.open("/tmp/halite.log", "a")))
Halite.logging(for: "halite.file", skip_benchmark: true, colorize: false)
.get("http://httpbin.org/get", params: {name: "foobar"})
# Write json data to a log file
Log.setup("halite.file", backend: Log::IOBackend.new(File.open("/tmp/halite.log", "a")))
Halite.logging(format: "json", for: "halite.file")
.get("http://httpbin.org/get", params: {name: "foobar"})
# Redirect *all* logging from Halite to a file:
Log.setup("halite", backend: Log::IOBackend.new(File.open("/tmp/halite.log", "a")))
```
#### Use the custom logging
Creating the custom logging by integration `Halite::Logging::Abstract` abstract class.
Here has two methods must be implement: `#request` and `#response`.
```crystal
class CustomLogging < Halite::Logging::Abstract
def request(request)
@logger.info { "| >> | %s | %s %s" % [request.verb, request.uri, request.body] }
end
def response(response)
@logger.info { "| << | %s | %s %s" % [response.status_code, response.uri, response.content_type] }
end
end
# Add to adapter list (optional)
Halite::Logging.register "custom", CustomLogging.new
Halite.logging(logging: CustomLogging.new)
.get("http://httpbin.org/get", params: {name: "foobar"})
# We can also call it use format name if you added it.
Halite.logging(format: "custom")
.get("http://httpbin.org/get", params: {name: "foobar"})
# => 2017-12-13 16:40:13 +08:00 | >> | GET | http://httpbin.org/get?name=foobar
# => 2017-12-13 16:40:15 +08:00 | << | 200 | http://httpbin.org/get?name=foobar application/json
```
### Local Cache
Local cache feature is caching responses easily with Halite through an chainable method that is simple and elegant
yet powerful. Its aim is to focus on the HTTP part of caching and do not worrying about how stuff stored, api rate limiting
even works without network(offline).
It has the following options:
- `file`: Load cache from file. it conflict with `path` and `expires`.
- `path`: The path of cache, default is "/tmp/halite/cache/"
- `expires`: The expires time of cache, default is never expires.
- `debug`: The debug mode of cache, default is `true`
With debug mode, cached response it always included some headers information:
- `X-Halite-Cached-From`: Cache source (cache or file)
- `X-Halite-Cached-Key`: Cache key with verb, uri and body (return with cache, not `file` passed)
- `X-Halite-Cached-At`: Cache created time
- `X-Halite-Cached-Expires-At`: Cache expired time (return with cache, not `file` passed)
```crystal
Halite.use("cache").get "http://httpbin.org/anything" # request a HTTP
r = Halite.use("cache").get "http://httpbin.org/anything" # request from local storage
r.headers # => {..., "X-Halite-Cached-At" => "2018-08-30 10:41:14 UTC", "X-Halite-Cached-By" => "Halite", "X-Halite-Cached-Expires-At" => "2018-08-30 10:41:19 UTC", "X-Halite-Cached-Key" => "2bb155e6c8c47627da3d91834eb4249a"}}
```
### Link Headers
Many HTTP APIs feature [Link headers](https://tools.ietf.org/html/rfc5988). GitHub uses
these for [pagination](https://developer.github.com/v3/#pagination) in their API, for example:
```crystal
r = Halite.get "https://api.github.com/users/icyleaf/repos?page=1&per_page=2"
r.links
# => {"next" =>
# => Halite::HeaderLink(
# => @params={},
# => @rel="next",
# => @target="https://api.github.com/user/17814/repos?page=2&per_page=2"),
# => "last" =>
# => Halite::HeaderLink(
# => @params={},
# => @rel="last",
# => @target="https://api.github.com/user/17814/repos?page=41&per_page=2")}
r.links["next"]
# => "https://api.github.com/user/17814/repos?page=2&per_page=2"
r.links["next"].params
# => {}
```
## Help and Discussion
You can browse the API documents:
https://icyleaf.github.io/halite/
You can browse the all chainable methods:
https://icyleaf.github.io/halite/Halite/Chainable.html
You can browse the Changelog:
https://github.com/icyleaf/halite/blob/master/CHANGELOG.md
If you have found a bug, please create a issue here:
https://github.com/icyleaf/halite/issues/new
## How to Contribute
Your contributions are always welcome! Please submit a pull request or create an issue to add a new question, bug or feature to the list.
All [Contributors](https://github.com/icyleaf/halite/graphs/contributors) are on the wall.
## You may also like
- [totem](https://github.com/icyleaf/totem) - Load and parse a configuration file or string in JSON, YAML, dotenv formats.
- [markd](https://github.com/icyleaf/markd) - Yet another markdown parser built for speed, Compliant to CommonMark specification.
- [poncho](https://github.com/icyleaf/poncho) - A .env parser/loader improved for performance.
- [popcorn](https://github.com/icyleaf/popcorn) - Easy and Safe casting from one type to another.
- [fast-crystal](https://github.com/icyleaf/fast-crystal) - 💨 Writing Fast Crystal 😍 -- Collect Common Crystal idioms.
## License
[MIT License](https://github.com/icyleaf/halite/blob/master/LICENSE) © icyleaf
================================================
FILE: shard.yml
================================================
name: halite
version: 0.12.1
authors:
- icyleaf