From f3c0db54c7631d5038b7ebf95adee6344bf7a395 Mon Sep 17 00:00:00 2001 From: Amit Kumar Singh Date: Fri, 1 Apr 2022 15:03:09 +0530 Subject: [PATCH] Backmerge 31 03 2022 (#1171) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Doc: Add outline (#815) * doc: add categories * refactor: doc structure refactored * fix: package-lock.json removed * refactor: create outline sub-directories * refactor: rename test to testing * Update netty-all to 4.1.73.Final (#811) * Disable benchmarks comment on fork pull request (#820) * Disable benchmarks on fork PR * run benchmarks on fork PR but disbable PR comment * Feature: API to modify headers (#824) * feat(Headers):added new api to update headers * renamed api * Feature: Signed Cookie (#751) * feat(cookie): added secret in cookie * feat(cookie): added signcookie middleware * feat(cookie): scalafmt * fix(cookie): sign cookie while encoding * scalafmt * fix(Cookie): added unsign method for cookie * fix(cookie): minor changes * fix(signCookieMiddleware: simplified signCookies * fix(cookie): removed try catch from signContent * cookie: throw error in verify * cookie: throw error in verify * verify method changes * fixed test cases * fix: removed decodeResponseSignedCookie * fix: middlewareSpec * added modifyheaders in middleware * removed unwanted changes * scalafmt * refactoring * refactoring * build fix * build fix * fix: decodeResponseCookie * added modify * Update sbt-scalafix to 0.9.34 (#805) * Fix: Echo streaming (#828) * Failing test * Fix echo streaming * Pr Comments * Docs: Update Basic Examples (#814) * doc(Getting started): updated examples * docs: updated basic examples * docs: update advanced examples (#816) * maintenance: semanticdb revision usage (#832) * maintenance: workflow scala version (#833) * Fix: HasHeader bug (#835) * #834 - fix and test for the bug. * applied suggested change * refactor: fix naming for Http operators (#839) * Update sbt-bloop to 1.4.12 (#810) * Update sbt-bloop to 1.4.12 * Update sbt-bloop to 1.4.12 * Update scala-library to 2.13.8 (#801) * Update scala-library to 2.13.8 * Regenerate workflow with sbt-github-actions * Add configuration builder methods to zhttp.service.Server (#768) * Add configuration builder methods to zhttp.service.Server * Update zio-http/src/main/scala/zhttp/service/Server.scala Co-authored-by: Tushar Mathur * Update zio-http/src/main/scala/zhttp/service/Server.scala Co-authored-by: Tushar Mathur * Update zio-http/src/main/scala/zhttp/service/Server.scala Co-authored-by: Tushar Mathur * Change some server with* builder methods (enable parameter) * Server withAcceptContinue(enabled) Co-authored-by: Tushar Mathur * maintenance: html template for internal server error string (#851) Closes #842 * Performance: Improve benchmarking code (#731) * wip: try server codec without validation * wip: remove flush consolidator * wip: use wrapped buffer * wip: add flush consolidator * perf: make response encoding checks faster * use encoder and decoder (#733) * wip: remove Request creation * use encoder and decoder Co-authored-by: Tushar Mathur * disable object aggregator * disable object aggregator * revert disable object aggregator * doc: update scala doc * perf: freeze the HttpResponse Co-authored-by: Amit Kumar Singh Co-authored-by: amitsingh * Refactor: Support middlewares on Http (#773) * refactor: remove type-params from Response * chore: self review * refactor: rename Middleware to HttpMiddleware * refactor: add `@@@` to Http as an alternative to `@@`. * feature: add new Middleware API * feature: add `flatten` and `ifThenElse` * feature: add `ifThenElseZIO` * refactor: fix type params for `identity` * feature: add `when` * feature: add `make` constructor * refactor: make middleware methods final * refactor: git remains * refactor: implement HttpMiddleware as Middleware * scala3 fix * Refactor CORS middleware (#788) * Refactor/merge middleware and http middleware (#790) * Refactor move cors and timeout * move some httpMiddlewares to Middleware * move some AuthMiddleware to Middleware * move remaining AuthMiddleware to Middleware * move Middlewares to middleware package * scaladoc * codec example * move Middleware to http package * named alias for `@@` * rename Auth to AuthMiddlewares * rename CORSMiddleware to CorsMiddlewares * rename CSRF to CsrfMiddlewares * make primitives private * rename MiddlewareExtensions to HttpMiddlewares * rename operators in HttpMiddlewares * scalaDoc * arg rename * doc update and general refactor * simplify cors middleware * rename CorsConfig * renames * Make middlewares package private MiddlewareRequest * Introduce MiddlewareRequest (#798) * Introduce MiddlewareRequest * PR review comments * Refactor move runAfter to Middleware * refactor: add `UMiddleware` * feature: add `contramapZIO` * refactor: move cors config to Cors file * refactor: rename files * refactor: remove AuthSpec from WebSpec * refactor: fix naming for Http operators * refactor: add partial type suport for contraMapZIO * Refactor: Codec (#841) * Add Run Before (#840) * Add Run Before * Add Run Before and After * use renamed operator * refactor: add partial type suport for contraMap * Implement missing operators in Middleware (#807) * Implement missing operators in Middleware * fix as operator * headers Middleware changes * sign cookie * extend with HeaderExtensions * rename suite * PR comments * refactor: use `Request` instead of `MiddlewareRequest` * refactor: rename methods * refactor: resolve fix me issue Co-authored-by: amitsingh Co-authored-by: Amit Kumar Singh * refactor: rename ClientParams to ClientRequest (#856) * refactor: use declarative encoding for http.middleware (#869) * Move docs to v1.x directory (#861) * Bug Add host in client from absolute URL (#847) * feat(Client): Add host in client from absolute URL * feat(Client): Refactor and add spec for host value * feat(Client): fmt formatting * feat(Client): fmt formatting * feat(Client): use setHeaders from netty * feat(Client): Add spec for host * feat(Client): Add spec for host * feat(Client): Rename clientParams to clientRequest * Doc website fix (#871) * Update scala3-library to 3.1.1 (#873) * Update scala3-library to 3.1.1 * Regenerate workflow with sbt-github-actions * Doc: Fix getting started link (#874) * Update scalafmt-core to 3.3.2 (#864) * Doc: `Headers` documentation (#817) * doc: add categories * refactor: doc structure refactored * fix: package-lock.json removed * refactor: create outline sub-directories * initial draft on Headers documentaiton * refactor: rename test to testing * migrated Headers doc in the right structure * re-organized the section. added more examples on the service, client and middleware side. * updated documentation * updated based on review * more updates * more updates * minor changes * minor changes * Update docs/website/docs/v1.x/dsl/headers/index.md * Update docs/website/docs/v1.x/dsl/headers/index.md Co-authored-by: Shubham Girdhar Co-authored-by: amitsingh Co-authored-by: Amit Kumar Singh * Fix collapsible Headers Doc (#876) * Test: Added Integration tests for `HExit.Success` (#852) * feat: added validation app for HExit without zio * feat: removed type from validationAppSpec * test: Iterating using HttpGen.Method * test: rearranged status method params * test: name fixes * Update scalafmt-core to 3.3.3 (#881) * Update scalafmt-core to 3.3.3 (#884) * Fix: EncodeClientParams (#868) * fix: encodeClientParams * test(client): added test for req url string * test(client): variable name changed * test: added test in encodeClientSpec * simplifies test case * doc: add comment on why relative path needs to be used. Co-authored-by: Tushar Mathur * Refactor: HttpRunnableSpec clean up (#857) * refactor: reduce HttpRunnableSpec boilerplate * refactor: remove all helpers from HttpRunnableSpec and inline the method * doc: update documentation for test module * refactor: add type-constraints to Http for specialized methods * refactor: use IsResponse type constraint * style(*): apply scala fmt * doc: update documentation * update doc (#897) * Docs: Getting started (#887) * fix: added more content * fixed comments * fix: getting started * modifications * added more * minor changes * minor changes * changes * refactor: rename asString to encode in URI (#898) * refactor: rename asString to encode in Scheme (#904) * fix: example links (#906) * Update getting-started.md (#907) * refactor: rename asString to encode in Scheme (#905) * Doc: setup (#886) * doc: setup * resolve: PR comments * Update netty-incubator-transport-native-io_uring to 0.0.12.Final (#908) * Documentation for Server (#885) * removed config.md and make configurations part of Server page * added server configurations * markdwon appearing fine in docusaurus generated page * added one missing configuration * Server documentation changed according to PR comments * change note to tip Co-authored-by: Sumant Awasthi Co-authored-by: amitsingh * Performance: Improve performance of `collectM` (#882) * Use ZIO response * fix: improve cancellation performance * refactor: use sticky server * refactor: reduce allocations * revert example * Doc: Added <> operator in getting started (#910) * fix: ++ operator doc * fix: composition * feature: Add `collectManaged` to Http (#909) * Add collectManaged * comment typo * feature: add `echo` operator to `Socket` (#900) * feature: add `Socket.empty` (#901) * feature: add `Socket.empty` * test: add unit test case for Socket#empty Co-authored-by: Shubham Girdhar * feature: add `toHttp` to Socket.scala (#902) * feature: add `toHttp` to Socket.scala * test: add unit test for `toApp` to Socket.scala Co-authored-by: Shubham Girdhar * refactor: merge Request for Client and Server (#894) * Revert "refactor: merge Request for Client and Server (#894)" (#915) This reverts commit fc8b9b5a155e7c59b548d191265d30e0e6b61d8b. * Feature: add `toHttp` to Response (#903) * feature: add `wrapHttp` to Response * test: add unit test for wrapHttp in Response.scala * refactor: operator name changed to `toHttp` Co-authored-by: Shubham Girdhar * Update scalafmt-core to 3.4.0 (#920) * Update sbt to 1.6.2 (#931) * Remove outdated benchmark.md (#940) * removed benchmark file * removed benchmark hyperlink from readme * refactor: rename `getHeaders` to `headers` in `ClientRequest` (#928) * refactor: rename `getHeaders` to `headers` in `ClientRequest` * fix: compiler errors * fix: compiler errors * Refactor: rename `asString` to `encode` in `Path` (#927) * refactor: rename `asString` to `encode` in Path * fix: compiler errors * Disable flow Control (#854) * Add builder pattern for URL (#930) * Add builder pattern for URL * Compare encoded value to string literal * Documentation: Http (#888) * initial commit for http documentation * refactor: adding bold formatting for HTTP and resolving review comments * refactor: Chnage app to application in http documentation * refactor:fixed review comments * added combinators section * style: formatting the code blocks * added getBodyAsString to http documentation * added HttpApp section * fixing typos * added mapZIO and contamapZIO documentation for http * provide layer documentation for http * doc: added a secion- Running an HttpApp Co-authored-by: Dino John * feature: add `Http.apply` (#949) * Refactor: Drop `toZIO` and `wrapZIO` APIs (#950) * feature: add `Http.apply` * refactor: remove ZIO wrapping APIs * Fix: Server KeepAlive true by default and enable client to set Http version on requests (#792) * IT cases for KeepAlive included in ServerConfigSpec * Allow possibility of modifying server conf in HttpRunnableSpec * reverted Header.scala * reverted Header.scala * reverted Header.scala * struggling with fmt * pull latest from main * reverting changes in ci files * included review recommendations * Concise test description * removed extra comments * readjusted ServerConfigSpec * enable keep alive by default; added more test cases for Http 1.0; tweaked test cases and description * removed configurableServe * removed unnecessary lines; shorter tests * reverted Server disabled and some spec changes * Test/keepalive httpversion (#800) * make httpVersion first param in request methods * tweaked test cases even further using requestHeaderValueByName * changed names * resolved comments, and more tweaking of test * deleted extra line Co-authored-by: Sumant Awasthi * Renamed ServerConfigSpec to KeepAliveSpec * made httpVersion first param in ClientParams, made relevant changes * in EncodeClientParams use http version from ClientParams * a minor change * changed server keepAlive = true by default, also rebased with main Co-authored-by: Sumant Awasthi * feat: url add `isAbsolute` and `isRelative` operators (#946) * refactor: add type-params * feat: url add `isAbsolute` and `isRelative` operators # Conflicts: # zio-http/src/main/scala/zhttp/http/URL.scala * WebSocket Client Support (#933) * wip: websocket client support * test: add unit test for WebSocket using native websocket client * fix: fromZIO in websocket client spec * refactor: queue usage simplified * feat: websocket client support * refactor: imports optimized * doc: for ClientSocketHandler * refactor: bootstrap method * websocket client with response support * feat: wss support * refactor: SocketProtocol * refactor: add SocketClient example: add WebSocketSimpleClient * refactor: SocketProtocol with type evidence refactor: general clean up * test: for Scheme * refactor: rename asString to encode in URI * refactor: remove example of SecureClient * refactor: url doesn't need to be part of the protocol. Url can be passed as an additional parameter for now. This simplifies the overall design. * refactor: socket client for now will take the complete url * refactor: update client example * refactor: add `connect` operator to SocketApp * refactor: use `url` as a string instead of URL type * refactor: re-order methods * style(*): apply scala fmt * revert: unnecessary changes * feature: add `echo` operator to `Socket` * refactor: update type info * feature: add `Socket.empty` * feature: add `wrapHttp` to Response * feature: add `toHttp` to Socket.scala * style(*): apply scala fmt * refactor: rename asString to encode in Scheme * refactor: cleanup Scheme changes * test: update test assertion * refactor: combine server and client handlers * refactor: add hint in touch for ClientSocketUpgradeHandler * refactor: change package for SocketAppHandler * refactor: rename methods in SocketProtocol * fix: add host header and port in Request automatically * fix: example links (#906) * Update getting-started.md (#907) * refactor: rename asString to encode in Scheme (#905) * Doc: setup (#886) * doc: setup * resolve: PR comments * Update netty-incubator-transport-native-io_uring to 0.0.12.Final (#908) * Documentation for Server (#885) * removed config.md and make configurations part of Server page * added server configurations * markdwon appearing fine in docusaurus generated page * added one missing configuration * Server documentation changed according to PR comments * change note to tip Co-authored-by: Sumant Awasthi Co-authored-by: amitsingh * Performance: Improve performance of `collectM` (#882) * Use ZIO response * fix: improve cancellation performance * refactor: use sticky server * refactor: reduce allocations * revert example * Doc: Added <> operator in getting started (#910) * fix: ++ operator doc * fix: composition * feature: Add `collectManaged` to Http (#909) * Add collectManaged * comment typo * feature: add `echo` operator to `Socket` (#900) * feature: add `Socket.empty` (#901) * feature: add `Socket.empty` * test: add unit test case for Socket#empty Co-authored-by: Shubham Girdhar * refactor: add attribute to Request created by Handler * refactor: remove "case" from ClientInboundHandler * refactor: rename to WebSocketAppHandler * feature: add `toHttp` to Socket.scala (#902) * feature: add `toHttp` to Socket.scala * test: add unit test for `toApp` to Socket.scala Co-authored-by: Shubham Girdhar * cleaning up residue files * refactor: rename `asString` to `encode` in Path * refactor: rename `getHeaders` to `headers` in `ClientRequest` * refactor: simplify client API * refactor: update client params encoder * style: ordering methods in URL.scala * refactor: ClientRequest will throw if the url is invalid * refactor: pass URL instead of String in ClientRequest * test: fix `GetBodyAsStringSpec` * style(*): apply scala fmt * refactor: fix invalid url being passed in HttpRunnableSpec * refactor: Move SSL requirements to ClientAttirbute * feature: add `scheme` operator on URL * feature: add `IsWebSocket` and `isSecure` operators on Scheme * refactor: use named imports in WebSocketAppHandler * test: fix base url for websockets * refactor: update how flags are set inside client * test: add non-zio spec to the spec list * refactor: fix client test * style: scalafmt error and remove TODO * refactor: remove unused handler * refactor: remove host header changes and RequestSpec (#945) * refactor: move java scheme generators to SchemeSpec (#944) * refactor: move java scheme generators to SchemeSpec * refactor: resolve PR comments * feat: url add `isAbsolute` and `isRelative` operators * refactor: rename EncodeClientParams.scala to EncodeClientRequest * fix: handle errors on client connection * style(*): apply scala fmt * refactor: simplify multiple websocket upgrades test (#948) * refactor: resolve PR comments style: fmt fix * refactor: general refactor and style changes Co-authored-by: Shubham Girdhar Co-authored-by: Shruti Verma <62893271+ShrutiVerma97@users.noreply.github.com> Co-authored-by: Amit Kumar Singh Co-authored-by: Scala Steward <43047562+scala-steward@users.noreply.github.com> Co-authored-by: sumawa Co-authored-by: Sumant Awasthi Co-authored-by: amitsingh Co-authored-by: James Beem * Documentation: Request (#926) * docs: request * doc: added client request docs * added query param section * doc: refactoring * WIP * fix: doc * fix: doc * request * request * example fixed * Fix ssl issue due to missing peer host port hint (#952) * feature: add connect operator on Socket (#955) * Style: Update scala doc wrapping (#959) * chore: update scalafmt config * style(*): apply scala fmt * remove get prefix (#958) * Update scalafmt-core to 3.4.1 (#960) * Update scalafmt-core to 3.4.2 (#962) * Update scalafmt-core to 3.4.2 (#961) * remove declarative API (#957) * Update sbt-updates to 0.6.2 (#966) * Doc: Readme (#970) * fix: readme * changed note * style: rearrange methods in files (#963) * Doc: Cookie (#974) * doc: cookie * doc: cookie * cookie: changes * refactor: drop `CanBeSilent` type-constraint from Http (#964) * refactor: drop `CanBeSilent` type-constraint from Http * update usage of `silent` operator * Feature: add `Version` (#965) * refactor: rename `asHttpMethod` to `toJava` * refactor: add `Version` domain * style(*): apply scala fmt * Fix: Fire response instead of error in HExit.Failure() (#980) * Bug fix: fixes #979 * Made changes to nonZIOSpec in ServerSpec to include tests for HExit.Failure() * fix: typo in bug_report.md (#981) * Update netty-all to 4.1.74.Final (#982) * refactor: remove sealed modifier from Middleware trait (#984) * refactor: rename method on Client (#985) * fix: request doc (#989) * Use a Gen of methods without HEAD for Responses with content (#992) * doc: setup g8 (#988) * doc: setup g8 * refactor: bump zhttp version * Doc: for WebSocketFrame (#953) * doc: for WebSocketFrame * resolve: PR comments * resolve: PR comments * Documentation for HttpData (#987) * Documentation for HttpData * added server and client side usage of HttpData * Update docs/website/docs/v1.x/dsl/http-data/index.md Co-authored-by: Amit Kumar Singh * refactor: rename `provide` and `provideSome` (#996) * Fix: `Content-Type` header gets incorrect values. (#899) * initial work * Fix tests * Perf enhancements * compilation error fix * rename getContentType * Update zio-http/src/main/scala/zhttp/http/MediaType.scala Co-authored-by: Tushar Mathur * Make MediaType private, and final. Co-authored-by: Tushar Mathur * Feat: Http from HExit (#986) * added 2 operators to create http from HExit * added tests for collectHExit and fromFunctionHExit * rewrote nonZIO app in serverSpec using collectHExit * Refactor: Remove Collect and FromFunctionZIO (#1002) * Refactor: Implemented FromFunctionZIO using FromFunctionHExit * Refactor: Implement Collect using FromFunctionHExit * Fix: Binary WebSocketFrame (#1005) * fix: change binary websocket frame arg to Chunk[Byte] from ByteBuf Closes #1004 * test: add unit test for binary websocketframe * style: fix formatting * feature: add `fromFileZIO` (#1010) * refactor: `fromFile` uses `fromFileZIO` * feature: add `Http.fromResource` (#1009) * Refactor: Content Type Fixes (#1008) * style: scalafmt fixes * style: sorting alphabetically * fix: set conten-type only if not set already * refactor: make internal fields private * doc: add todo * refactor: make caching optional * refactor: use `Http.fromResource` in test * refactor: fix test * doc: fix binary constructor usage (#1014) * Add data in `Request` trait (#1017) * maintenance: run scalafmt once (#1027) * Build(deps): Bump follow-redirects in /docs/website (#1029) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update scalafmt-core to 3.4.3 (#1022) * Remove tuple from ResponseHandler (#1033) * Remove tuple from ResponseHandler * cleanup * restructuring (#1040) * added more examples (#1038) * Doc: Response (#967) * updated with main * added operators * revert * setstatus change * renaming (#1047) * Fix: Releasing request twice (#1046) * refactor: not releasing request * Added failing test * optimize path encode method (#1035) * Feature: Static Server (#1024) * WIP : static server from file path * fix CI errrors * format * Basic functionalities work without options * Fix errors on java8 * Fix errors on java8 * Fix refactoring error * clean up * Fixig tests * working around file type detection bug * Method not allowed. * Better comments. * Update zio-http/src/main/scala/zhttp/http/Http.scala Co-authored-by: Tushar Mathur * Refactor * More tests. * Fix PR comments * Moved headers to fromPath * Update zio-http/src/main/scala/zhttp/http/Http.scala Co-authored-by: Tushar Mathur * Update zio-http/src/main/scala/zhttp/http/Http.scala Co-authored-by: Tushar Mathur * Fixes. * Fixing tests * Update zio-http/src/main/scala/zhttp/http/HttpData.scala Co-authored-by: Tushar Mathur * Update zio-http/src/main/scala/zhttp/http/HttpData.scala Co-authored-by: Tushar Mathur * Update zio-http/src/main/scala/zhttp/http/Http.scala Co-authored-by: Tushar Mathur * PR is closed * Used Http.fromFile in implementation * Remove unwanted commits * Fixing compile issues. * feature: add `withMediaType` operator in HeaderModifier * refactor: drop `setMediaType` from Response * refactor: inline file creation * refactor: simplify implementation for fromFile and fromFileZIO * doc: add todo comments * refactor: update listFilesHtml implementation * refactor: update scaladoc * test: fix refactored misses * test: use `+` instead of `,` * style: scalafmt * feature: add `mediaType` to HeaderGetters * feature: add Http.attempt` operator * refactor: MediaType works on extensions directly * refactor: add tests for fromResource * test: StaticFileServerSpec add tests for directory * refactor: make `fromPath` use `fromFile` internally * refactor: simplify `fromPath` * refactor: update scala docs for Http operators * refactor: fix type info of `Http.response` * feature: add `listDirectory` operator * feature: add StyledContainerHtml * refactor: drop `Util` * refactor: inline util HTML constructors * test: add test for invalid file size * doc: update comments * style: reorder methods in Http Co-authored-by: ashprakasan * Revert "Feature: Static Server (#1024)" (#1060) This reverts commit 33ac08d1a528143dfbcfcbee2d3202b31abbbd30. * Performance: Use `CharSequence` for ServerTime (#1064) * feature: use CharSequence in for ServerTime * style: scalafmt * Feature: add `dropLast` to Path (#1065) * feature: add `dropLast` to Path * style: scalafmt * feature: Response.html now supports taking in Status also (#1067) * feature: add `code` method on Status (#1068) * refactor: fix type info of `Http.response` (#1073) * feature: add `foldCause` to HttpError (#1066) * feature: add `withMediaType` operator in HeaderModifier (#1071) * Feature: add `Http.attempt` operator (#1070) * feature: add Http.attempt` operator * test: add tests for Http.attempt * feature: add `getResource` and `getResourceAsFile` operators on Http (#1074) * Update jwt-core to 9.0.4 (#1056) * Update sbt-bloop to 1.4.13 (#1055) * Build(deps): Bump url-parse from 1.5.4 to 1.5.7 in /docs/website (#1062) Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.4 to 1.5.7. - [Release notes](https://github.com/unshiftio/url-parse/releases) - [Commits](https://github.com/unshiftio/url-parse/compare/1.5.4...1.5.7) --- updated-dependencies: - dependency-name: url-parse dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feature: add `ifModifiedSinceDecoded` (#1075) * Feature: Static Server (#1061) * WIP : static server from file path * fix CI errrors * format * Basic functionalities work without options * Fix errors on java8 * Fix errors on java8 * Fix refactoring error * clean up * Fixig tests * working around file type detection bug * Method not allowed. * Better comments. * Update zio-http/src/main/scala/zhttp/http/Http.scala Co-authored-by: Tushar Mathur * Refactor * More tests. * Fix PR comments * Moved headers to fromPath * Update zio-http/src/main/scala/zhttp/http/Http.scala Co-authored-by: Tushar Mathur * Update zio-http/src/main/scala/zhttp/http/Http.scala Co-authored-by: Tushar Mathur * Fixes. * Fixing tests * Update zio-http/src/main/scala/zhttp/http/HttpData.scala Co-authored-by: Tushar Mathur * Update zio-http/src/main/scala/zhttp/http/HttpData.scala Co-authored-by: Tushar Mathur * Update zio-http/src/main/scala/zhttp/http/Http.scala Co-authored-by: Tushar Mathur * PR is closed * Used Http.fromFile in implementation * Remove unwanted commits * Fixing compile issues. * feature: add `withMediaType` operator in HeaderModifier * refactor: drop `setMediaType` from Response * refactor: inline file creation * refactor: simplify implementation for fromFile and fromFileZIO * doc: add todo comments * refactor: update listFilesHtml implementation * refactor: update scaladoc * test: fix refactored misses * test: use `+` instead of `,` * style: scalafmt * feature: add `mediaType` to HeaderGetters * feature: add Http.attempt` operator * refactor: MediaType works on extensions directly * refactor: add tests for fromResource * test: StaticFileServerSpec add tests for directory * refactor: make `fromPath` use `fromFile` internally * refactor: simplify `fromPath` * refactor: update scala docs for Http operators * refactor: fix type info of `Http.response` * feature: add `listDirectory` operator * feature: add StyledContainerHtml * refactor: drop `Util` * refactor: inline util HTML constructors * test: add test for invalid file size * doc: update comments * style: reorder methods in Http * refactor: improve style of StyledContainerHtml * refactor: drop directory support * refactor: drop directory listing * refactor: add a color to background * feature: add `getResource` and `getResourceAsFile` operators on Http * feature: add `foldCause` to HttpError * feature: add `code` method on Status * feature: StyleContainerHtml max-width updated * feature: Handler uses `HttpError` for empty responses * feature: Response.html now supports taking in Status also * feature: beautify Response.fromHttpError * refactor: rename file to Template * feature: add `dropLast` to Path * feature: add `Http.template` operator * example: update static server example * style: scalafmt updates * feature: use CharSequence in for ServerTime * feature: add `ifModifiedSinceDecoded` * feature: add `ifModifiedSinceDecoded` * feature: add `ifModifiedSinceDecoded` Co-authored-by: ashprakasan * issue 715 Support custom ChannelInitializer (#932) * doc: socket (#1036) * doc: socket * refactor: clean up * refactor: resolve PR comment * fix: resolve PR comment and fix typo in `merge` * feat: added new constructor in http (#1077) * Feature: Add `when` operator in `Http` (#1078) * perf: added when operator in http * build fix * removed jrequest from constructor * fmt * unsafeEncode: HttpRequest * added when primitive * refactor: rename `whenPath` to `whenPathEq` Co-authored-by: Tushar Mathur * Feature: Effectful Auth Middleware (#1079) * added effectful athu middlewares * added tests * refactor: stopped calling tuple of credentials as Header * added credentials * Test: Server Settings Support in `HttpRunnableSpec#serve` (#1053) * feat: server settings support in HttpRunnableSpec#serve * resolve: PR comments * refactor: website docs sidebar pages repositioned (#1049) * Feat: Server Request Decompression (#1095) * feat: server request decompression * test: add test for deflate * Feature: Added helper checkers to `Status` class (#1058) * Added helper checkers to Status class * For status, formatted, removed and made them def Co-authored-by: Roberto Leibman * Build(deps): Bump prismjs from 1.26.0 to 1.27.0 in /docs/website (#1100) Bumps [prismjs](https://github.com/PrismJS/prism) from 1.26.0 to 1.27.0. - [Release notes](https://github.com/PrismJS/prism/releases) - [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md) - [Commits](https://github.com/PrismJS/prism/compare/v1.26.0...v1.27.0) --- updated-dependencies: - dependency-name: prismjs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Build(deps): Bump url-parse from 1.5.7 to 1.5.10 in /docs/website (#1103) Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.7 to 1.5.10. - [Release notes](https://github.com/unshiftio/url-parse/releases) - [Commits](https://github.com/unshiftio/url-parse/compare/1.5.7...1.5.10) --- updated-dependencies: - dependency-name: url-parse dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * added try catch in handler to catch throwable apps (#1099) * removed flatten from test (#1109) * Feature: Request Streaming (#1048) * introduce `Incoming` and `Outgoing` inHttpData * streaming support * benchmark disable objectAggregator * cleanup * refactor * cleanup + PR comments * cleanup + PR comments * cleanup + PR comments * refactor: rename variable * memory leak * refactor: Handler now extends ChannelInboundHandlerAdapter * refactor: remove unused methods from UnsafeChannel * remove bodyAsCharSequenceStream operator * refactor: remove unnecessary methods on HttpData * refactor: re-implement `bodyAsStream` * refactor: remove unsafe modification of pipeline from HttpData * refactor: rename HttpData types * fix 2.12 build * refactor: remove type param * PR comment * PR comment * refaector: simplify releaseRequest * refactor: reorder methods in ServerResponseHandler * refactor: make methods final * refactor: rename HttpData traits * add `bodyAsByteArray` and derive `body` and `bodyAsString` from it. * add test: should throw error for HttpData.Incoming * Introduce `useAggregator` method on settings and use it everywhere * remove sharable from `ServerResponseHandler` * Update zio-http/src/main/scala/zhttp/http/Request.scala * refactor: remove unnecessary pattern matching * throw exception on unknown message type * simplify test * refactor: change order of ContentHandler. Move it before the RequestHandler * test: update test structure * refactor: move pattern match logic to WebSocketUpgrade * revert addBefore Change because of degrade in performance (#1089) * fix static server issue with streaming * take case of auto read if body is not used * autoRead when needed * Update zio-http/src/main/scala/zhttp/service/RequestBodyHandler.scala * Update zio-http/src/main/scala/zhttp/http/Response.scala * Update zio-http/src/main/scala/zhttp/http/Response.scala * remove test which is not used * Update zio-http/src/main/scala/zhttp/service/Handler.scala Co-authored-by: Shrey Mehta <36622672+smehta91@users.noreply.github.com> * Update zio-http/src/main/scala/zhttp/service/Handler.scala Co-authored-by: Shrey Mehta <36622672+smehta91@users.noreply.github.com> * style: fmt * exclude Head in 404 check Co-authored-by: Tushar Mathur Co-authored-by: Shrey Mehta <36622672+smehta91@users.noreply.github.com> * Fix toByteBuf for streamed HttpData (#1118) * Fix toByteBuf for streamed HttpData * example: more secure string compare for login (#1120) * Feature: Introduced defect channel for `Http` (#1083) * Introduced defect channel for Http and added helpful combinators for error handling * Reformat code * Removed doubtful combinator Http.run * Simplified implementation of catchNonFatalOrDie * Simplified implementation of catchSome * Guarded Http.execute() with try-catch block in order to convert unexpectable exceptions to defects * Added test to check Http.catchSomeDefect catches throws defects * Fixed warnings about shadowing type parameters * Fix: fold over defect cause * Formatted HttpSpec.scala * Perf: catch defects only where they may occur * Fmt Co-authored-by: Nikolay Artamonov * Enhancement: Merge server and client Responses (#1111) * Merge client and server Request * Remove IsResponse * rename getBodyAsString => bodyAsString in doc (#1124) * removed links from scaladoc (#1127) * Feature: Add `version` to `Request` (#1094) * introduce `Incoming` and `Outgoing` inHttpData * streaming support * benchmark disable objectAggregator * cleanup * refactor * cleanup + PR comments * cleanup + PR comments * cleanup + PR comments * refactor: rename variable * memory leak * refactor: Handler now extends ChannelInboundHandlerAdapter * refactor: remove unused methods from UnsafeChannel * remove bodyAsCharSequenceStream operator * refactor: remove unnecessary methods on HttpData * refactor: re-implement `bodyAsStream` * refactor: remove unsafe modification of pipeline from HttpData * refactor: rename HttpData types * fix 2.12 build * refactor: remove type param * PR comment * PR comment * refaector: simplify releaseRequest * refactor: reorder methods in ServerResponseHandler * refactor: make methods final * refactor: rename HttpData traits * add `bodyAsByteArray` and derive `body` and `bodyAsString` from it. * add test: should throw error for HttpData.Incoming * Introduce `useAggregator` method on settings and use it everywhere * remove sharable from `ServerResponseHandler` * Update zio-http/src/main/scala/zhttp/http/Request.scala * refactor: remove unnecessary pattern matching * throw exception on unknown message type * simplify test * refactor: change order of ContentHandler. Move it before the RequestHandler * test: update test structure * refactor: move pattern match logic to WebSocketUpgrade * revert addBefore Change because of degrade in performance (#1089) * fix static server issue with streaming * Introduce version in Request * Delete `Request.make` * add missing scaladoc Co-authored-by: Tushar Mathur * Refactor: Merge client and server `Request` (#1125) * introduce `Incoming` and `Outgoing` inHttpData * streaming support * benchmark disable objectAggregator * cleanup * refactor * cleanup + PR comments * cleanup + PR comments * cleanup + PR comments * refactor: rename variable * memory leak * refactor: Handler now extends ChannelInboundHandlerAdapter * refactor: remove unused methods from UnsafeChannel * remove bodyAsCharSequenceStream operator * refactor: remove unnecessary methods on HttpData * refactor: re-implement `bodyAsStream` * refactor: remove unsafe modification of pipeline from HttpData * refactor: rename HttpData types * fix 2.12 build * refactor: remove type param * PR comment * PR comment * refaector: simplify releaseRequest * refactor: reorder methods in ServerResponseHandler * refactor: make methods final * refactor: rename HttpData traits * add `bodyAsByteArray` and derive `body` and `bodyAsString` from it. * add test: should throw error for HttpData.Incoming * Introduce `useAggregator` method on settings and use it everywhere * remove sharable from `ServerResponseHandler` * Update zio-http/src/main/scala/zhttp/http/Request.scala * refactor: remove unnecessary pattern matching * throw exception on unknown message type * simplify test * refactor: change order of ContentHandler. Move it before the RequestHandler * test: update test structure * refactor: move pattern match logic to WebSocketUpgrade * revert addBefore Change because of degrade in performance (#1089) * fix static server issue with streaming * Introduce version in Request * Merge Client and Server Request * Delete `Request.make` * Gen refactor * rename files * make function private * rename attribute * rename attribute Co-authored-by: Tushar Mathur * Refactor: Http.Status names to Camel Case (#1129) * support custom Status for HtppError * support custom Status for HttpError * updated code based on review. * fixed tests * changed from Capitalized Status names to Camel Case * proper camel case usage Co-authored-by: Gabriel Ciuloaica * Feature: Support Custom statuses code (#1121) * support custom Status for HtppError * support custom Status for HttpError * updated code based on review. * fixed tests * Refactor: Http.Status names to Camel Case (#1128) * changed from Capitalized Status names to Camel Case * proper camel case usage * Update zio-http/src/main/scala/zhttp/http/HttpError.scala Co-authored-by: Tushar Mathur * fixed after conflict * updated tests Co-authored-by: Tushar Mathur * Enhancement: Added combine operator (#1106) * enhancement: added combine operator * added test cases with three apps * refactor: plaintextBenchmarkServer * test case updated * removed flatten from test * test case updated * added test for collectHttp * simplified tests * added failing test * fixed test * refactor: cleaning up the execute method in Http Co-authored-by: Tushar Mathur * Refactor: Added lift in PartialCollect (#1105) * refactor: added lift in partialCollect * added text in path * refactor: reduce iterations of benchmarks Co-authored-by: Tushar Mathur * Feature: Bearer auth middleware (#1097) * added header constructor for bearer authorization * added jwt auth middlewares * updated the Auth examples * added tests * renamed the auth middleware from jwt to barer * refactor: example AuthenticationClient * added scala doc for examples, renamed variables in tests Co-authored-by: Shubham Girdhar Co-authored-by: amitsingh * Update netty-all to 4.1.75.Final (#1130) * Refactor: `Request` and `Response` to extend `HttpDataExtension` (#1112) * Request Streaming Example * refactor: add HttpDataExtension * doc: add documentation * fix: compiler errors Co-authored-by: Tushar Mathur * Refactor: Make `Http.getResource` consistent with `ZStream.fromResource` (#1113) * Fix `Http.getResource` Code based on https://github.com/zio/zio/blob/v1.0.13/streams/jvm/src/main/scala/zio/stream/platform.scala#L385-L398 * Improve `File` creation lazyness: The `File` instance will be created much later in the pipe * Fix tests * Fix tests * Update zio-http/src/main/scala/zhttp/http/Http.scala Co-authored-by: Tushar Mathur * Remove `Blocking` from the type signature as requested Co-authored-by: Tushar Mathur * Update netty-incubator-transport-native-io_uring to 0.0.13.Final (#1133) * Add more useful toString method for Request (#995) * Add more useful toString method for Request * Fmt * Moved toString to Request trait * Added a few tests of Request.toString * Fmt * Improved tests for Request.toString * Added Scala doc for Request.toString * Added protocol version to a string representation of Request * Fmt * fix(request): fix scala doc (#1138) * Performance: Improve HttpData toByteBuf (#1137) * refactor: add blocking layer for file based constructors * refactor: use JavaFile instead of RandomAccessFile * doc: fix StaticServer example * style: scalafmt * refactor: clean up HttpData * refactor: remove blocking * refactor: remove unnecessary overload in ResponseHandler * test: add timeout and use ByteBufConfig to control encoding * refactor: add ByteBufConfig * refactor: rename types internally * style: fmt Co-authored-by: amitsingh * Fix: Handle HttpClient Interruption on connection close (#1039) * the promise has to be uninteruptible to avoid loosing the response message in case the netty chanel is getting closed due to a unhandled exception * added a note about reasons why the promise is made uninterruptible. * New chapter: efficient development process (#1145) The dream11 gitter template already contains the most important sbt plugins to setup an efficient development process. To guide newcomers to zio-http, the newely added chapter gives an introduction on their purpose and how to use them. * do not watch reStop (#1146) Removed the tilde (~) before reStop, as it is not helpful to run this in watch-mode * Feature: add `Middleware.codecHttp` (#1141) * feature: add codecHttp * feature: add `codecMiddleware` to Http * added tests Co-authored-by: shrutiverma97 * Performance: Use `CharSequence` internally wherever possible (#1142) * refactor: add blocking layer for file based constructors * refactor: use JavaFile instead of RandomAccessFile * doc: fix StaticServer example * style: scalafmt * refactor: clean up HttpData * refactor: remove blocking * refactor: remove unnecessary overload in ResponseHandler * test: add timeout and use ByteBufConfig to control encoding * refactor: add ByteBufConfig * performance: use `AsciiString` inside of HttpData * performance: use CharSequence in `Http.template` and `Http.text` * performance: use `CharSequence` in Response.json, Response.text and Response.redirect * performance: use `CharSequence` in Html templates * refactor: add `bodyAsCharSequence` to HttpDataExtension * Update scala-collection-compat to 2.7.0 (#1154) * Update jwt-core to 9.0.5 (#1150) * feature: add `narrow` operator to Http (#1161) * feature: add `toHttp` method to `Response` (#1160) * refactor: DynamicServer is not bound to HttpEnv only (#1159) * fix: Socket.end implementation (#1158) * Update sbt-scala3-migrate to 0.5.1 (#1163) Co-authored-by: Shubham Girdhar Co-authored-by: Shruti Verma <62893271+ShrutiVerma97@users.noreply.github.com> Co-authored-by: Gabriel Ciuloaica <95849448+gciuloaica@users.noreply.github.com> Co-authored-by: Tushar Mathur Co-authored-by: Javier Goday Co-authored-by: Tushar Mathur Co-authored-by: Brendan McKee Co-authored-by: kaushik143 Co-authored-by: zsfVishnu-d11 <66246684+zsfVishnu-d11@users.noreply.github.com> Co-authored-by: Scala Steward <43047562+scala-steward@users.noreply.github.com> Co-authored-by: sumawa Co-authored-by: Sumant Awasthi Co-authored-by: James Beem Co-authored-by: Dino Babu John <66246799+dinojohn@users.noreply.github.com> Co-authored-by: Dino John Co-authored-by: AshPrakasan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: RAJKUMAR NATARAJAN Co-authored-by: Roberto Leibman Co-authored-by: Roberto Leibman Co-authored-by: Shrey Mehta <36622672+smehta91@users.noreply.github.com> Co-authored-by: ex0ns Co-authored-by: Guillaume Massé Co-authored-by: Nikolay Artamonov Co-authored-by: Nikolay Artamonov Co-authored-by: Gabriel Ciuloaica Co-authored-by: Jules Ivanic Co-authored-by: Dani Rey Co-authored-by: shrutiverma97 --- docs/website/docs/getting-started.md | 133 --------- docs/website/docs/v1.x/dsl/http.md | 2 +- docs/website/docs/v1.x/dsl/server.md | 4 +- .../advanced-examples/advanced_server.md | 2 +- .../zio-http-basic-examples/https_client.md | 2 +- docs/website/docs/v1.x/getting-started.md | 2 +- docs/website/docs/v1.x/index.md | 33 +++ .../example/PlainTextBenchmarkServer.scala | 2 +- project/Dependencies.scala | 2 +- project/plugins.sbt | 2 +- zio-http/src/main/scala/zhttp/html/Dom.scala | 14 +- .../src/main/scala/zhttp/html/Elements.scala | 6 +- zio-http/src/main/scala/zhttp/html/Html.scala | 4 +- .../src/main/scala/zhttp/html/Template.scala | 2 +- zio-http/src/main/scala/zhttp/http/Http.scala | 194 +++++++------ .../src/main/scala/zhttp/http/HttpData.scala | 266 ++++++++++++++---- .../scala/zhttp/http/HttpDataExtension.scala | 23 +- .../main/scala/zhttp/http/Middleware.scala | 30 +- .../src/main/scala/zhttp/http/Request.scala | 10 + .../src/main/scala/zhttp/http/Response.scala | 114 ++++---- .../main/scala/zhttp/service/Handler.scala | 2 +- .../scala/zhttp/service/HttpRuntime.scala | 2 +- .../handlers/ServerResponseHandler.scala | 68 +++-- .../src/test/scala/zhttp/html/DomSpec.scala | 4 +- .../test/scala/zhttp/http/HttpDataSpec.scala | 56 ++-- .../src/test/scala/zhttp/http/HttpSpec.scala | 19 ++ .../scala/zhttp/http/MiddlewareSpec.scala | 19 ++ .../test/scala/zhttp/http/RequestSpec.scala | 28 ++ .../test/scala/zhttp/http/ResponseSpec.scala | 40 +++ .../scala/zhttp/internal/DynamicServer.scala | 59 ++-- .../test/scala/zhttp/internal/HttpGen.scala | 10 +- .../zhttp/internal/HttpRunnableSpec.scala | 23 +- .../test/scala/zhttp/internal/package.scala | 8 + 33 files changed, 715 insertions(+), 470 deletions(-) delete mode 100644 docs/website/docs/getting-started.md create mode 100644 zio-http/src/test/scala/zhttp/http/RequestSpec.scala create mode 100644 zio-http/src/test/scala/zhttp/internal/package.scala diff --git a/docs/website/docs/getting-started.md b/docs/website/docs/getting-started.md deleted file mode 100644 index 0524799b71..0000000000 --- a/docs/website/docs/getting-started.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Getting Started - -## Http - -### Creating a "_Hello World_" app - -```scala -import zhttp.http._ - -val app = Http.text("Hello World!") -``` - -An application can be made using any of the available operators on `zhttp.Http`. In the above program for any Http request, the response is always `"Hello World!"`. - -### Routing - -```scala -import zhttp.http._ - -val app = Http.collect[Request] { - case Method.GET -> !! / "fruits" / "a" => Response.text("Apple") - case Method.GET -> !! / "fruits" / "b" => Response.text("Banana") -} -``` - -Pattern matching on route is supported by the framework - -### Composition - -```scala -import zhttp.http._ - -val a = Http.collect[Request] { case Method.GET -> !! / "a" => Response.ok } -val b = Http.collect[Request] { case Method.GET -> !! / "b" => Response.ok } - -val app = a <> b -``` - -Apps can be composed using the `<>` operator. The way it works is, if none of the routes match in `a` , or a `NotFound` error is thrown from `a`, and then the control is passed on to the `b` app. - -### ZIO Integration - -```scala -val app = Http.collectZIO[Request] { - case Method.GET -> !! / "hello" => Response.text("Hello World").wrapZIO -} -``` - -`Http.collectZIO` allow routes to return a ZIO effect value. - -### Accessing the Request - -```scala -import zhttp.http._ - -val app = Http.collectZIO[Request] { - case req @ Method.GET -> !! / "fruits" / "a" => - Response.text("URL:" + req.url.path.asString + " Headers: " + req.getHeaders).wrapZIO - case req @ Method.POST -> !! / "fruits" / "a" => - req.getBodyAsString.map(Response.text(_)) - } -``` - -### Testing - -zhttp provides a `zhttp-test` package for use in unit tests. You can utilize it as follows: - -```scala -import zio.test._ -import zhttp.test._ -import zhttp.http._ - -object Spec extends DefaultRunnableSpec { - - def spec = suite("http")( - test("should be ok") { - val app = Http.ok - val req = Request() - assertM(app(req))(equalTo(Response.ok)) // an apply method is added via `zhttp.test` package - } - ) -} -``` - -## Socket - -### Creating a socket app - -```scala -import zhttp.socket._ - -private val socket = Socket.collect[WebSocketFrame] { case WebSocketFrame.Text("FOO") => - ZStream.succeed(WebSocketFrame.text("BAR")) - } - - private val app = Http.collectZIO[Request] { - case Method.GET -> !! / "greet" / name => Response.text(s"Greetings {$name}!").wrapZIO - case Method.GET -> !! / "ws" => socket.toResponse - } -``` - -## Server - -### Starting an Http App - -```scala -import zhttp.http._ -import zhttp.service.Server -import zio._ - -object HelloWorld extends App { - val app = Http.ok - - override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = - Server.start(8090, app).exitCode -} -``` - -A simple Http app that responds with empty content and a `200` status code is deployed on port `8090` using `Server.start`. - -## Examples - -- [Simple Server](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/HelloWorld.scala) -- [Advanced Server](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/HelloWorldAdvanced.scala) -- [WebSocket Server](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/SocketEchoServer.scala) -- [Streaming Response](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/StreamingResponse.scala) -- [Simple Client](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/SimpleClient.scala) -- [File Streaming](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/FileStreaming.scala) -- [Authentication](https://github.com/dream11/zio-http/blob/main/example/src/main/scala/Authentication.scala) diff --git a/docs/website/docs/v1.x/dsl/http.md b/docs/website/docs/v1.x/dsl/http.md index 64af2a87ce..33e5514de2 100644 --- a/docs/website/docs/v1.x/dsl/http.md +++ b/docs/website/docs/v1.x/dsl/http.md @@ -229,7 +229,7 @@ The below snippet tests an app that takes `Int` as input and responds by adding object Spec extends DefaultRunnableSpec { def spec = suite("http")( - test("1 + 1 = 2") { + testM("1 + 1 = 2") { val app: Http[Any, Nothing, Int, Int] = Http.fromFunction[Int](_ + 1) assertM(app(1))(equalTo(2)) } diff --git a/docs/website/docs/v1.x/dsl/server.md b/docs/website/docs/v1.x/dsl/server.md index 30564897b3..33310d0993 100644 --- a/docs/website/docs/v1.x/dsl/server.md +++ b/docs/website/docs/v1.x/dsl/server.md @@ -31,7 +31,7 @@ This section describes, ZIO HTTP Server and different configurations you can pro override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = { server.make .use(start => - Console.printLine(s"Server started on port ${start.port}") + console.putStrLn(s"Server started on port ${start.port}") *> ZIO.never, ).provideCustomLayer(ServerChannelFactory.auto ++ EventLoopGroup.auto(2)) .exitCode @@ -84,7 +84,7 @@ object HelloWorldAdvanced extends App { server.make .use(_ => // Waiting for the server to start - Console.printLine(s"Server started on port $PORT") + console.putStrLn(s"Server started on port $PORT") // Ensures the server doesn't die after printing *> ZIO.never, diff --git a/docs/website/docs/v1.x/examples/advanced-examples/advanced_server.md b/docs/website/docs/v1.x/examples/advanced-examples/advanced_server.md index 39976c8a0b..a49feb9487 100644 --- a/docs/website/docs/v1.x/examples/advanced-examples/advanced_server.md +++ b/docs/website/docs/v1.x/examples/advanced-examples/advanced_server.md @@ -35,7 +35,7 @@ object HelloWorldAdvanced extends App { server.make .use(start => // Waiting for the server to start - Console.printLine(s"Server started on port ${start.port}") + console.putStrLn(s"Server started on port ${start.port}") // Ensures the server doesn't die after printing *> ZIO.never, diff --git a/docs/website/docs/v1.x/examples/zio-http-basic-examples/https_client.md b/docs/website/docs/v1.x/examples/zio-http-basic-examples/https_client.md index 369bd3e881..854c191f9b 100644 --- a/docs/website/docs/v1.x/examples/zio-http-basic-examples/https_client.md +++ b/docs/website/docs/v1.x/examples/zio-http-basic-examples/https_client.md @@ -32,7 +32,7 @@ object HttpsClient extends App { val program = for { res <- Client.request(url, headers, sslOption) data <- res.bodyAsString - _ <- Console.printLine { data } + _ <- console.putStrLn { data } } yield () override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] diff --git a/docs/website/docs/v1.x/getting-started.md b/docs/website/docs/v1.x/getting-started.md index dbfe29616b..626260202e 100644 --- a/docs/website/docs/v1.x/getting-started.md +++ b/docs/website/docs/v1.x/getting-started.md @@ -108,7 +108,7 @@ import zhttp.http._ object Spec extends DefaultRunnableSpec { def spec = suite("http")( - test("should be ok") { + testM("should be ok") { val app = Http.ok val req = Request() assertM(app(req))(equalTo(Response.ok)) diff --git a/docs/website/docs/v1.x/index.md b/docs/website/docs/v1.x/index.md index 7b61c7ad64..6937c5745f 100644 --- a/docs/website/docs/v1.x/index.md +++ b/docs/website/docs/v1.x/index.md @@ -42,3 +42,36 @@ sbt new dream11/zio-http.g8 * [scalafix-organize-imports](https://github.com/liancheng/scalafix-organize-imports) * [sbt-revolver](https://github.com/spray/sbt-revolver) +## Efficient development process + +The dependencies in the Dream11 g8 template were added to enable an efficient development process. + +### sbt-revolver "hot-reload" changes + +Sbt-revolver can watch application resources for change and automatically re-compile and then re-start the application under development. This provides a fast development-turnaround, the closest you can get to real hot-reloading. + +Start your application from _sbt_ with the following command + +```shell +~reStart +``` + +Pressing enter will stop watching for changes, but not stop the application. Use the following command to stop the application (shutdown hooks will not be executed). + +``` +reStop +``` + +In case you already have an _sbt_ server running, i.e. to provide your IDE with BSP information, use _sbtn_ instead of _sbt_ to run `~reStart`, this let's both _sbt_ sessions share one server. + +### scalafmt automatically format source code + +scalafmt will automatically format all source code and assert that all team members use consistent formatting. + +### scalafix refactoring and linting + +scalafix will mainly be used as a linting tool during everyday development, for example by removing unused dependencies or reporting errors for disabled features. Additionally it can simplify upgrades of Scala versions and dependencies, by executing predefined migration paths. + +### sbt-native-packager + +sbt-native-packager can package the application in the most popular formats, for example Docker images, rpm packages or graalVM native images. diff --git a/example/src/main/scala/example/PlainTextBenchmarkServer.scala b/example/src/main/scala/example/PlainTextBenchmarkServer.scala index 392b835cf8..4da1215ba6 100644 --- a/example/src/main/scala/example/PlainTextBenchmarkServer.scala +++ b/example/src/main/scala/example/PlainTextBenchmarkServer.scala @@ -1,7 +1,7 @@ package example import io.netty.util.AsciiString -import zhttp.http._ +import zhttp.http.{Http, _} import zhttp.service.server.ServerChannelFactory import zhttp.service.{EventLoopGroup, Server} import zio.{ExitCode, UIO, URIO, ZIOAppDefault} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 5216868778..afeb0b2e12 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -4,7 +4,7 @@ object Dependencies { val JwtCoreVersion = "9.0.4" val NettyVersion = "4.1.75.Final" val NettyIncubatorVersion = "0.0.13.Final" - val ScalaCompactCollectionVersion = "2.6.0" + val ScalaCompactCollectionVersion = "2.7.0" val ZioVersion = "2.0.0-RC2" val SttpVersion = "3.3.18" diff --git a/project/plugins.sbt b/project/plugins.sbt index f16a3b5332..e6a85d62a5 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -5,5 +5,5 @@ addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.2") addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.14.2") -addSbtPlugin("ch.epfl.scala" % "sbt-scala3-migrate" % "0.5.0") +addSbtPlugin("ch.epfl.scala" % "sbt-scala3-migrate" % "0.5.1") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") diff --git a/zio-http/src/main/scala/zhttp/html/Dom.scala b/zio-http/src/main/scala/zhttp/html/Dom.scala index 04408600f2..f1f2940525 100644 --- a/zio-http/src/main/scala/zhttp/html/Dom.scala +++ b/zio-http/src/main/scala/zhttp/html/Dom.scala @@ -10,7 +10,7 @@ package zhttp.html * elements. */ sealed trait Dom { self => - def encode: String = self match { + def encode: CharSequence = self match { case Dom.Element(name, children) => val attributes = children.collect { case self: Dom.Attribute => self.encode } @@ -35,19 +35,19 @@ sealed trait Dom { self => } object Dom { - def attr(name: String, value: String): Dom = Dom.Attribute(name, value) + def attr(name: CharSequence, value: CharSequence): Dom = Dom.Attribute(name, value) - def element(name: String, children: Dom*): Dom = Dom.Element(name, children) + def element(name: CharSequence, children: Dom*): Dom = Dom.Element(name, children) def empty: Dom = Empty - def text(data: String): Dom = Dom.Text(data) + def text(data: CharSequence): Dom = Dom.Text(data) - private[zhttp] final case class Element(name: String, children: Seq[Dom]) extends Dom + private[zhttp] final case class Element(name: CharSequence, children: Seq[Dom]) extends Dom - private[zhttp] final case class Text(data: String) extends Dom + private[zhttp] final case class Text(data: CharSequence) extends Dom - private[zhttp] final case class Attribute(name: String, value: String) extends Dom + private[zhttp] final case class Attribute(name: CharSequence, value: CharSequence) extends Dom private[zhttp] object Empty extends Dom } diff --git a/zio-http/src/main/scala/zhttp/html/Elements.scala b/zio-http/src/main/scala/zhttp/html/Elements.scala index be870a4d84..f1f8731b32 100644 --- a/zio-http/src/main/scala/zhttp/html/Elements.scala +++ b/zio-http/src/main/scala/zhttp/html/Elements.scala @@ -250,12 +250,12 @@ trait Elements { } object Element { - private[zhttp] val voidElementNames: Set[String] = + private[zhttp] val voidElementNames: Set[CharSequence] = Set(area, base, br, col, embed, hr, img, input, link, meta, param, source, track, wbr).map(_.name) - private[zhttp] def isVoid(name: String): Boolean = voidElementNames.contains(name) + private[zhttp] def isVoid(name: CharSequence): Boolean = voidElementNames.contains(name) - case class PartialElement(name: String) { + case class PartialElement(name: CharSequence) { def apply(children: Html*): Dom = Dom.element( name, children.collect { diff --git a/zio-http/src/main/scala/zhttp/html/Html.scala b/zio-http/src/main/scala/zhttp/html/Html.scala index d5dbc72141..2f004a750b 100644 --- a/zio-http/src/main/scala/zhttp/html/Html.scala +++ b/zio-http/src/main/scala/zhttp/html/Html.scala @@ -6,7 +6,7 @@ import scala.language.implicitConversions * A view is a domain that used generate HTML. */ sealed trait Html { self => - def encode: String = { + def encode: CharSequence = { self match { case Html.Empty => "" case Html.Single(element) => element.encode @@ -16,7 +16,7 @@ sealed trait Html { self => } object Html { - implicit def fromString(string: String): Html = Html.Single(Dom.text(string)) + implicit def fromString(string: CharSequence): Html = Html.Single(Dom.text(string)) implicit def fromSeq(elements: Seq[Dom]): Html = Html.Multiple(elements) diff --git a/zio-http/src/main/scala/zhttp/html/Template.scala b/zio-http/src/main/scala/zhttp/html/Template.scala index 95d9f0ace8..25e65bae96 100644 --- a/zio-http/src/main/scala/zhttp/html/Template.scala +++ b/zio-http/src/main/scala/zhttp/html/Template.scala @@ -5,7 +5,7 @@ package zhttp.html */ object Template { - def container(heading: String)(element: Html): Html = { + def container(heading: CharSequence)(element: Html): Html = { html( head( title(s"ZIO Http - ${heading}"), diff --git a/zio-http/src/main/scala/zhttp/http/Http.scala b/zio-http/src/main/scala/zhttp/http/Http.scala index 65517fb8c5..f225086e43 100644 --- a/zio-http/src/main/scala/zhttp/http/Http.scala +++ b/zio-http/src/main/scala/zhttp/http/Http.scala @@ -7,7 +7,7 @@ import zhttp.html._ import zhttp.http.headers.HeaderModifier import zhttp.service.server.ServerTime import zhttp.service.{Handler, HttpRuntime, Server} -import zio.ZIO.attemptBlockingIO +import zio.ZIO.attemptBlocking import zio._ import zio.stream.ZStream @@ -27,6 +27,78 @@ sealed trait Http[-R, +E, -A, +B] extends (A => ZIO[R, Option[E], B]) { self => import Http._ + /** + * Extracts body as a ByteBuf + */ + private[zhttp] final def bodyAsByteBuf(implicit + eb: B <:< Response, + ee: E <:< Throwable, + ): Http[R, Throwable, A, ByteBuf] = + self.widen[Throwable, B].mapZIO(_.bodyAsByteBuf) + + /** + * Evaluates the app and returns an HExit that can be resolved further + * + * NOTE: `execute` is not a stack-safe method for performance reasons. Unlike + * ZIO, there is no reason why the execute should be stack safe. The + * performance improves quite significantly if no additional heap allocations + * are required this way. + */ + final private[zhttp] def execute(a: A): HExit[R, E, B] = + self match { + + case Http.Empty => HExit.empty + case Http.Identity => HExit.succeed(a.asInstanceOf[B]) + case Succeed(b) => HExit.succeed(b) + case Fail(e) => HExit.fail(e) + case Die(e) => HExit.die(e) + case Attempt(a) => + try { HExit.succeed(a()) } + catch { case e: Throwable => HExit.fail(e.asInstanceOf[E]) } + case FromFunctionHExit(f) => + try { f(a) } + catch { case e: Throwable => HExit.die(e) } + case FromHExit(h) => h + case Chain(self, other) => self.execute(a).flatMap(b => other.execute(b)) + case Race(self, other) => + (self.execute(a), other.execute(a)) match { + case (HExit.Effect(self), HExit.Effect(other)) => + Http.fromOptionFunction[Any](_ => self.raceFirst(other)).execute(a) + case (HExit.Effect(_), other) => other + case (self, _) => self + } + case FoldHttp(self, ee, df, bb, dd) => + try { + self.execute(a).foldExit(ee(_).execute(a), df(_).execute(a), bb(_).execute(a), dd.execute(a)) + } catch { + case e: Throwable => HExit.die(e) + } + + case RunMiddleware(app, mid) => + try { + mid(app).execute(a) + } catch { + case e: Throwable => HExit.die(e) + } + + case When(f, other) => + try { + if (f(a)) other.execute(a) else HExit.empty + } catch { + case e: Throwable => HExit.die(e) + } + + case Combine(self, other) => { + self.execute(a) match { + case HExit.Empty => other.execute(a) + case exit: HExit.Success[_] => exit.asInstanceOf[HExit[R, E, B]] + case exit: HExit.Failure[_] => exit.asInstanceOf[HExit[R, E, B]] + case exit: HExit.Die => exit + case exit @ HExit.Effect(_) => exit.defaultWith(other.execute(a)).asInstanceOf[HExit[R, E, B]] + } + } + } + /** * Attaches the provided middleware to the Http app */ @@ -34,6 +106,13 @@ sealed trait Http[-R, +E, -A, +B] extends (A => ZIO[R, Option[E], B]) { self => mid: Middleware[R1, E1, A1, B1, A2, B2], ): Http[R1, E1, A2, B2] = mid(self) + /** + * Combines two Http instances into a middleware that works a codec for + * incoming and outgoing messages. + */ + final def \/[R1 <: R, E1 >: E, C, D](other: Http[R1, E1, C, D]): Middleware[R1, E1, B, C, A, D] = + self codecMiddleware other + /** * Alias for flatmap */ @@ -164,6 +243,13 @@ sealed trait Http[-R, +E, -A, +B] extends (A => ZIO[R, Option[E], B]) { self => ): Http[R1, E1, A1, B1] = unrefineWith(pf)(Http.fail).catchAll(e => e) + /** + * Combines two Http instances into a middleware that works a codec for + * incoming and outgoing messages. + */ + final def codecMiddleware[R1 <: R, E1 >: E, C, D](other: Http[R1, E1, C, D]): Middleware[R1, E1, B, C, A, D] = + Middleware.codecHttp(self, other) + /** * Collects some of the results of the http and converts it to another type. */ @@ -532,82 +618,16 @@ sealed trait Http[-R, +E, -A, +B] extends (A => ZIO[R, Option[E], B]) { self => self.asInstanceOf[Http[R, E1, A, B1]] /** - * Combines the two apps and returns the result of the one on the right + * Narrows the type of the input */ - final def zipRight[R1 <: R, E1 >: E, A1 <: A, C1](other: Http[R1, E1, A1, C1]): Http[R1, E1, A1, C1] = - self.flatMap(_ => other) + final def narrow[A1](implicit a: A1 <:< A): Http[R, E, A1, B] = + self.asInstanceOf[Http[R, E, A1, B]] /** - * Extracts body as a ByteBuf - */ - private[zhttp] final def bodyAsByteBuf(implicit - eb: B <:< Response, - ee: E <:< Throwable, - ): Http[R, Throwable, A, ByteBuf] = - self.widen[Throwable, B].mapZIO(_.bodyAsByteBuf) - - /** - * Evaluates the app and returns an HExit that can be resolved further - * - * NOTE: `execute` is not a stack-safe method for performance reasons. Unlike - * ZIO, there is no reason why the execute should be stack safe. The - * performance improves quite significantly if no additional heap allocations - * are required this way. + * Combines the two apps and returns the result of the one on the right */ - final private[zhttp] def execute(a: A): HExit[R, E, B] = - self match { - - case Http.Empty => HExit.empty - case Http.Identity => HExit.succeed(a.asInstanceOf[B]) - case Succeed(b) => HExit.succeed(b) - case Fail(e) => HExit.fail(e) - case Die(e) => HExit.die(e) - case Attempt(a) => - try { HExit.succeed(a()) } - catch { case e: Throwable => HExit.fail(e.asInstanceOf[E]) } - case FromFunctionHExit(f) => - try { f(a) } - catch { case e: Throwable => HExit.die(e) } - case FromHExit(h) => h - case Chain(self, other) => self.execute(a).flatMap(b => other.execute(b)) - case Race(self, other) => - (self.execute(a), other.execute(a)) match { - case (HExit.Effect(self), HExit.Effect(other)) => - Http.fromOptionFunction[Any](_ => self.raceFirst(other)).execute(a) - case (HExit.Effect(_), other) => other - case (self, _) => self - } - case FoldHttp(self, ee, df, bb, dd) => - try { - self.execute(a).foldExit(ee(_).execute(a), df(_).execute(a), bb(_).execute(a), dd.execute(a)) - } catch { - case e: Throwable => HExit.die(e) - } - - case RunMiddleware(app, mid) => - try { - mid(app).execute(a) - } catch { - case e: Throwable => HExit.die(e) - } - - case When(f, other) => - try { - if (f(a)) other.execute(a) else HExit.empty - } catch { - case e: Throwable => HExit.die(e) - } - - case Combine(self, other) => { - self.execute(a) match { - case HExit.Empty => other.execute(a) - case exit: HExit.Success[_] => exit.asInstanceOf[HExit[R, E, B]] - case exit: HExit.Failure[_] => exit.asInstanceOf[HExit[R, E, B]] - case exit: HExit.Die => exit - case exit @ HExit.Effect(_) => exit.defaultWith(other.execute(a)).asInstanceOf[HExit[R, E, B]] - } - } - } + final def zipRight[R1 <: R, E1 >: E, A1 <: A, C1](other: Http[R1, E1, A1, C1]): Http[R1, E1, A1, C1] = + self.flatMap(_ => other) } object Http { @@ -615,6 +635,15 @@ object Http { implicit final class HttpAppSyntax[-R, +E](val http: HttpApp[R, E]) extends HeaderModifier[HttpApp[R, E]] { self => + private[zhttp] def compile[R1 <: R]( + zExec: HttpRuntime[R1], + settings: Server.Config[R1, Throwable], + serverTimeGenerator: ServerTime, + )(implicit + evE: E <:< Throwable, + ): ChannelHandler = + Handler(http.asInstanceOf[HttpApp[R1, Throwable]], zExec, settings, serverTimeGenerator) + /** * Patches the response produced by the app */ @@ -654,15 +683,6 @@ object Http { * Applies Http based on the path as string */ def whenPathEq(p: String): HttpApp[R, E] = http.when(_.unsafeEncode.uri().contentEquals(p)) - - private[zhttp] def compile[R1 <: R]( - zExec: HttpRuntime[R1], - settings: Server.Config[R1, Throwable], - serverTimeGenerator: ServerTime, - )(implicit - evE: E <:< Throwable, - ): ChannelHandler = - Handler(http.asInstanceOf[HttpApp[R1, Throwable]], zExec, settings, serverTimeGenerator) } /** @@ -783,7 +803,7 @@ object Http { /** * Creates an Http app from the contents of a file. */ - def fromFile(file: => java.io.File): HttpApp[Any, Throwable] = Http.fromFileZIO(Task(file)) + def fromFile(file: => java.io.File): HttpApp[Any, Throwable] = Http.fromFileZIO(UIO(file)) /** * Creates an Http app from the contents of a file which is produced from an @@ -888,7 +908,7 @@ object Http { */ def getResource(path: String): Http[Any, Throwable, Any, net.URL] = Http - .fromZIO(attemptBlockingIO(getClass.getClassLoader.getResource(path))) + .fromZIO(attemptBlocking(getClass.getClassLoader.getResource(path))) .flatMap { resource => if (resource == null) Http.empty else Http.succeed(resource) } /** @@ -953,14 +973,14 @@ object Http { * Creates an Http app which responds with an Html page using the built-in * template. */ - def template(heading: String)(view: Html): HttpApp[Any, Nothing] = + def template(heading: CharSequence)(view: Html): HttpApp[Any, Nothing] = Http.response(Response.html(Template.container(heading)(view))) /** * Creates an Http app which always responds with the same plain text. */ - def text(str: String, charset: Charset = HTTP_CHARSET): HttpApp[Any, Nothing] = - Http.succeed(Response.text(str, charset)) + def text(charSeq: CharSequence): HttpApp[Any, Nothing] = + Http.succeed(Response.text(charSeq)) /** * Creates an Http app that responds with a 408 status code after the provided diff --git a/zio-http/src/main/scala/zhttp/http/HttpData.scala b/zio-http/src/main/scala/zhttp/http/HttpData.scala index b3a5faa80c..bc3b97166c 100644 --- a/zio-http/src/main/scala/zhttp/http/HttpData.scala +++ b/zio-http/src/main/scala/zhttp/http/HttpData.scala @@ -3,7 +3,8 @@ package zhttp.http import io.netty.buffer.{ByteBuf, Unpooled} import io.netty.channel.ChannelHandlerContext import io.netty.handler.codec.http.{HttpContent, LastHttpContent} -import zio.ZIO.attemptBlocking +import io.netty.util.AsciiString +import zhttp.http.HttpData.ByteBufConfig import zio.stream.ZStream import zio.{Chunk, Task, UIO, ZIO} @@ -16,12 +17,23 @@ import java.nio.charset.Charset sealed trait HttpData { self => /** - * Returns true if HttpData is a stream + * Encodes the HttpData into a ByteBuf. Takes in ByteBufConfig to have a more + * fine grained control over the encoding. */ - final def isChunked: Boolean = self match { - case HttpData.BinaryStream(_) => true - case _ => false - } + def toByteBuf(config: ByteBufConfig): Task[ByteBuf] + + /** + * Encodes the HttpData into a Stream of ByteBufs. Takes in ByteBufConfig to + * have a more fine grained control over the encoding. + */ + def toByteBufStream(config: ByteBufConfig): ZStream[Any, Throwable, ByteBuf] + + /** + * Encodes the HttpData into a Http of ByeBuf. This could be more performant + * in certain cases. Takes in ByteBufConfig to have a more fine grained + * control over the encoding. + */ + def toHttp(config: ByteBufConfig): Http[Any, Throwable, Any, ByteBuf] /** * Returns true if HttpData is empty @@ -31,31 +43,48 @@ sealed trait HttpData { self => case _ => false } - final def toByteBuf: Task[ByteBuf] = { - self match { - case self: HttpData.Incoming => self.encode - case self: HttpData.Outgoing => self.encode - } - } + /** + * Encodes the HttpData into a ByteBuf. + */ + final def toByteBuf: Task[ByteBuf] = toByteBuf(ByteBufConfig.default) - final def toByteBufStream: ZStream[Any, Throwable, ByteBuf] = self match { - case self: HttpData.Incoming => self.encodeAsStream - case self: HttpData.Outgoing => ZStream.fromZIO(self.encode) - } + /** + * Encodes the HttpData into a Stream of ByteBufs + */ + final def toByteBufStream: ZStream[Any, Throwable, ByteBuf] = toByteBufStream(ByteBufConfig.default) + + /** + * A bit more efficient version of toByteBuf in certain cases + */ + final def toHttp: Http[Any, Throwable, Any, ByteBuf] = toHttp(ByteBufConfig.default) } object HttpData { + private def collectStream[R, E](stream: ZStream[R, E, ByteBuf]): ZIO[R, E, ByteBuf] = + stream.runFold(Unpooled.compositeBuffer()) { case (cmp, buf) => cmp.addComponent(true, buf) } + /** * Helper to create empty HttpData */ def empty: HttpData = Empty + /** + * Helper to create HttpData from AsciiString + */ + def fromAsciiString(asciiString: AsciiString): HttpData = FromAsciiString(asciiString) + /** * Helper to create HttpData from ByteBuf */ def fromByteBuf(byteBuf: ByteBuf): HttpData = HttpData.BinaryByteBuf(byteBuf) + /** + * Helper to create HttpData from CharSequence + */ + def fromCharSequence(charSequence: CharSequence, charset: Charset = HTTP_CHARSET): HttpData = + fromAsciiString(new AsciiString(charSequence, charset)) + /** * Helper to create HttpData from chunk of bytes */ @@ -64,9 +93,7 @@ object HttpData { /** * Helper to create HttpData from contents of a file */ - def fromFile(file: => java.io.File): HttpData = { - RandomAccessFile(() => new java.io.RandomAccessFile(file, "r")) - } + def fromFile(file: => java.io.File): HttpData = JavaFile(() => file) /** * Helper to create HttpData from Stream of string @@ -83,27 +110,20 @@ object HttpData { /** * Helper to create HttpData from String */ - def fromString(text: String, charset: Charset = HTTP_CHARSET): HttpData = Text(text, charset) - - private[zhttp] sealed trait Outgoing extends HttpData { self => - def encode: ZIO[Any, Throwable, ByteBuf] = - self match { - case HttpData.Text(text, charset) => UIO(Unpooled.copiedBuffer(text, charset)) - case HttpData.BinaryChunk(data) => UIO(Unpooled.copiedBuffer(data.toArray)) - case HttpData.BinaryByteBuf(data) => UIO(data) - case HttpData.Empty => UIO(Unpooled.EMPTY_BUFFER) - case HttpData.BinaryStream(stream) => - stream - .asInstanceOf[ZStream[Any, Throwable, ByteBuf]] - .runFold(Unpooled.compositeBuffer())((c, b) => c.addComponent(true, b)) - case HttpData.RandomAccessFile(raf) => - attemptBlocking { - val fis = new FileInputStream(raf().getFD) - val fileContent: Array[Byte] = new Array[Byte](raf().length().toInt) - fis.read(fileContent) - Unpooled.copiedBuffer(fileContent) - } - } + def fromString(text: String, charset: Charset = HTTP_CHARSET): HttpData = fromCharSequence(text, charset) + + private[zhttp] sealed trait Complete extends HttpData + + /** + * Provides a more fine grained control while encoding HttpData into ByteBUfs + */ + case class ByteBufConfig(chunkSize: Int = 1024 * 4) { + def chunkSize(fileLength: Long): Int = { + val actualInt = fileLength.toInt + if (actualInt < 0) chunkSize + else if (actualInt < chunkSize) actualInt + else chunkSize + } } private[zhttp] final class UnsafeContent(private val httpContent: HttpContent) extends AnyVal { @@ -116,9 +136,13 @@ object HttpData { def read(): Unit = ctx.read(): Unit } - private[zhttp] final case class Incoming(unsafeRun: (UnsafeChannel => UnsafeContent => Unit) => Unit) + private[zhttp] final case class UnsafeAsync(unsafeRun: (UnsafeChannel => UnsafeContent => Unit) => Unit) extends HttpData { - def encode: ZIO[Any, Nothing, ByteBuf] = for { + + /** + * Encodes the HttpData into a ByteBuf. + */ + override def toByteBuf(config: ByteBufConfig): Task[ByteBuf] = for { body <- ZIO.async[Any, Nothing, ByteBuf](cb => unsafeRun(ch => { val buffer = Unpooled.compositeBuffer() @@ -130,21 +154,149 @@ object HttpData { ) } yield body - def encodeAsStream: ZStream[Any, Nothing, ByteBuf] = ZStream - .async[Any, Nothing, ByteBuf](cb => - unsafeRun(ch => - msg => { - cb(ZIO.succeed(Chunk(msg.content))) - if (msg.isLast) cb(ZIO.fail(None)) else ch.read() - }, - ), - ) + /** + * Encodes the HttpData into a Stream of ByteBufs + */ + override def toByteBufStream(config: ByteBufConfig): ZStream[Any, Throwable, ByteBuf] = + ZStream + .async[Any, Nothing, ByteBuf](cb => + unsafeRun(ch => + msg => { + cb(ZIO.succeed(Chunk(msg.content))) + if (msg.isLast) cb(ZIO.fail(None)) else ch.read() + }, + ), + ) + + override def toHttp(config: ByteBufConfig): Http[Any, Throwable, Any, ByteBuf] = + Http.fromZIO(toByteBuf(config)) + } + + private[zhttp] case class FromAsciiString(asciiString: AsciiString) extends Complete { + + /** + * Encodes the HttpData into a ByteBuf. Takes in ByteBufConfig to have a + * more fine grained control over the encoding. + */ + override def toByteBuf(config: ByteBufConfig): Task[ByteBuf] = Task(Unpooled.wrappedBuffer(asciiString.array())) + + /** + * Encodes the HttpData into a Stream of ByteBufs. Takes in ByteBufConfig to + * have a more fine grained control over the encoding. + */ + override def toByteBufStream(config: ByteBufConfig): ZStream[Any, Throwable, ByteBuf] = + ZStream.fromZIO(toByteBuf(config)) + + /** + * Encodes the HttpData into a Http of ByeBuf. This could be more performant + * in certain cases. Takes in ByteBufConfig to have a more fine grained + * control over the encoding. + */ + override def toHttp(config: ByteBufConfig): Http[Any, Throwable, Any, ByteBuf] = ??? + } + + private[zhttp] final case class BinaryChunk(data: Chunk[Byte]) extends Complete { + + private def encode = Unpooled.wrappedBuffer(data.toArray) + + /** + * Encodes the HttpData into a ByteBuf. + */ + override def toByteBuf(config: ByteBufConfig): Task[ByteBuf] = UIO(encode) + + /** + * Encodes the HttpData into a Stream of ByteBufs + */ + override def toByteBufStream(config: ByteBufConfig): ZStream[Any, Throwable, ByteBuf] = + ZStream.fromZIO(toByteBuf(config)) + + override def toHttp(config: ByteBufConfig): UHttp[Any, ByteBuf] = Http.succeed(encode) + } + + private[zhttp] final case class BinaryByteBuf(data: ByteBuf) extends Complete { + + /** + * Encodes the HttpData into a ByteBuf. + */ + override def toByteBuf(config: ByteBufConfig): Task[ByteBuf] = Task(data) + + /** + * Encodes the HttpData into a Stream of ByteBufs + */ + override def toByteBufStream(config: ByteBufConfig): ZStream[Any, Throwable, ByteBuf] = + ZStream.fromZIO(toByteBuf(config)) + + override def toHttp(config: ByteBufConfig): UHttp[Any, ByteBuf] = Http.succeed(data) + } + + private[zhttp] final case class BinaryStream(stream: ZStream[Any, Throwable, ByteBuf]) extends Complete { + + /** + * Encodes the HttpData into a ByteBuf. + */ + override def toByteBuf(config: ByteBufConfig): Task[ByteBuf] = + collectStream(toByteBufStream(config)) + + /** + * Encodes the HttpData into a Stream of ByteBufs + */ + override def toByteBufStream(config: ByteBufConfig): ZStream[Any, Throwable, ByteBuf] = + stream + + override def toHttp(config: ByteBufConfig): Http[Any, Throwable, Any, ByteBuf] = + Http.fromZIO(toByteBuf(config)) + } + + private[zhttp] final case class JavaFile(unsafeFile: () => java.io.File) extends Complete { + + /** + * Encodes the HttpData into a ByteBuf. + */ + override def toByteBuf(config: ByteBufConfig): Task[ByteBuf] = + collectStream(toByteBufStream(config)) + + /** + * Encodes the HttpData into a Stream of ByteBufs + */ + override def toByteBufStream(config: ByteBufConfig): ZStream[Any, Throwable, ByteBuf] = + ZStream.unwrap { + for { + file <- Task(unsafeFile()) + fs <- Task(new FileInputStream(file)) + size = config.chunkSize(file.length()) + buffer = new Array[Byte](size) + } yield ZStream + .repeatZIOOption[Any, Throwable, ByteBuf] { + for { + len <- Task(fs.read(buffer)).mapError(Some(_)) + bytes <- if (len > 0) UIO(Unpooled.copiedBuffer(buffer, 0, len)) else ZIO.fail(None) + } yield bytes + } + .ensuring(UIO(fs.close())) + } + + override def toHttp(config: ByteBufConfig): Http[Any, Throwable, Any, ByteBuf] = + Http.fromZIO(toByteBuf(config)) + } + + object ByteBufConfig { + val default: ByteBufConfig = ByteBufConfig() + } + + private[zhttp] case object Empty extends Complete { + + /** + * Encodes the HttpData into a ByteBuf. + */ + override def toByteBuf(config: ByteBufConfig): Task[ByteBuf] = UIO(Unpooled.EMPTY_BUFFER) + + /** + * Encodes the HttpData into a Stream of ByteBufs + */ + override def toByteBufStream(config: ByteBufConfig): ZStream[Any, Throwable, ByteBuf] = + ZStream.fromZIO(toByteBuf(config)) + + override def toHttp(config: ByteBufConfig): UHttp[Any, ByteBuf] = Http.empty } - private[zhttp] final case class Text(text: String, charset: Charset) extends Outgoing - private[zhttp] final case class BinaryChunk(data: Chunk[Byte]) extends Outgoing - private[zhttp] final case class BinaryByteBuf(data: ByteBuf) extends Outgoing - private[zhttp] final case class BinaryStream(stream: ZStream[Any, Throwable, ByteBuf]) extends Outgoing - private[zhttp] final case class RandomAccessFile(unsafeGet: () => java.io.RandomAccessFile) extends Outgoing - private[zhttp] case object Empty extends Outgoing } diff --git a/zio-http/src/main/scala/zhttp/http/HttpDataExtension.scala b/zio-http/src/main/scala/zhttp/http/HttpDataExtension.scala index 7e08a4dde7..32daf83fcd 100644 --- a/zio-http/src/main/scala/zhttp/http/HttpDataExtension.scala +++ b/zio-http/src/main/scala/zhttp/http/HttpDataExtension.scala @@ -1,17 +1,15 @@ package zhttp.http import io.netty.buffer.{ByteBuf, ByteBufUtil} +import io.netty.util.AsciiString import zhttp.http.headers.HeaderExtension import zio.stream.ZStream -import zio.{Chunk, Task, UIO} +import zio.{Chunk, Task, UIO, ZIO} private[zhttp] trait HttpDataExtension[+A] extends HeaderExtension[A] { self: A => - def data: HttpData - private[zhttp] final def bodyAsByteBuf: Task[ByteBuf] = data.toByteBuf - final def bodyAsByteArray: Task[Array[Byte]] = - bodyAsByteBuf.flatMap(buf => Task(ByteBufUtil.getBytes(buf)).ensuring(UIO(buf.release(buf.refCnt())))) + def data: HttpData /** * Decodes the content of request as a Chunk of Bytes @@ -19,11 +17,14 @@ private[zhttp] trait HttpDataExtension[+A] extends HeaderExtension[A] { self: A final def body: Task[Chunk[Byte]] = bodyAsByteArray.map(Chunk.fromArray) + final def bodyAsByteArray: Task[Array[Byte]] = + bodyAsByteBuf.flatMap(buf => Task(ByteBufUtil.getBytes(buf)).ensuring(UIO(buf.release(buf.refCnt())))) + /** - * Decodes the content of request as string + * Decodes the content of request as CharSequence */ - final def bodyAsString: Task[String] = - bodyAsByteArray.map(new String(_, charset)) + final def bodyAsCharSequence: ZIO[Any, Throwable, CharSequence] = + bodyAsByteArray.map { buf => new AsciiString(buf, false) } /** * Decodes the content of request as stream of bytes @@ -37,4 +38,10 @@ private[zhttp] trait HttpDataExtension[+A] extends HeaderExtension[A] { self: A } } .flattenChunks + + /** + * Decodes the content of request as string + */ + final def bodyAsString: Task[String] = + bodyAsByteArray.map(new String(_, charset)) } diff --git a/zio-http/src/main/scala/zhttp/http/Middleware.scala b/zio-http/src/main/scala/zhttp/http/Middleware.scala index 74702043f9..5571be2d6d 100644 --- a/zio-http/src/main/scala/zhttp/http/Middleware.scala +++ b/zio-http/src/main/scala/zhttp/http/Middleware.scala @@ -1,7 +1,7 @@ package zhttp.http import zhttp.http.middleware.Web -import zio._ +import zio.{Clock, Duration, UIO, ZIO} /** * Middlewares are essentially transformations that one can apply on any Http to @@ -20,6 +20,11 @@ import zio._ */ trait Middleware[-R, +E, +AIn, -BIn, -AOut, +BOut] { self => + /** + * Applies middleware on Http and returns new Http. + */ + def apply[R1 <: R, E1 >: E](http: Http[R1, E1, AIn, BIn]): Http[R1, E1, AOut, BOut] + /** * Creates a new middleware that passes the output Http of the current * middleware as the input to the provided middleware. @@ -54,11 +59,6 @@ trait Middleware[-R, +E, +AIn, -BIn, -AOut, +BOut] { self => other(self(http)) } - /** - * Applies middleware on Http and returns new Http. - */ - def apply[R1 <: R, E1 >: E](http: Http[R1, E1, AIn, BIn]): Http[R1, E1, AOut, BOut] - /** * Makes the middleware resolve with a constant Middleware */ @@ -176,10 +176,15 @@ trait Middleware[-R, +E, +AIn, -BIn, -AOut, +BOut] { self => object Middleware extends Web { /** - * Creates a middleware using specified encoder and decoder + * Creates a middleware using the specified encoder and decoder functions */ def codec[A, B]: PartialCodec[A, B] = new PartialCodec[A, B](()) + /** + * Creates a codec middleware using two Http. + */ + def codecHttp[A, B]: PartialCodecHttp[A, B] = new PartialCodecHttp[A, B](()) + /** * Creates a middleware using specified effectful encoder and decoder */ @@ -326,6 +331,17 @@ object Middleware extends Web { Middleware.identity.mapZIO(encoder).contramapZIO(decoder) } + final class PartialCodecHttp[AOut, BIn](val unit: Unit) extends AnyVal { + def apply[R, E, AIn, BOut]( + decoder: Http[R, E, AOut, AIn], + encoder: Http[R, E, BIn, BOut], + ): Middleware[R, E, AIn, BIn, AOut, BOut] = + new Middleware[R, E, AIn, BIn, AOut, BOut] { + override def apply[R1 <: R, E1 >: E](http: Http[R1, E1, AIn, BIn]): Http[R1, E1, AOut, BOut] = + decoder >>> http >>> encoder + } + } + final class PartialContraMapZIO[-R, +E, +AIn, -BIn, -AOut, +BOut, AOut0]( val self: Middleware[R, E, AIn, BIn, AOut, BOut], ) extends AnyVal { diff --git a/zio-http/src/main/scala/zhttp/http/Request.scala b/zio-http/src/main/scala/zhttp/http/Request.scala index 6bf4736bff..7ec1a4cac6 100644 --- a/zio-http/src/main/scala/zhttp/http/Request.scala +++ b/zio-http/src/main/scala/zhttp/http/Request.scala @@ -78,6 +78,14 @@ trait Request extends HeaderExtension[Request] with HttpDataExtension[Request] { */ def setUrl(url: URL): Request = self.copy(url = url) + /** + * Returns a string representation of the request, useful for debugging, + * logging or other purposes. It contains the essential properties of HTTP + * request: protocol version, method, URL, headers and remote address. + */ + override def toString = + s"Request($version, $method, $url, $headers, $remoteAddress)" + /** * Gets the HttpRequest */ @@ -142,6 +150,8 @@ object Request { override def version: Version = req.version override def unsafeEncode: HttpRequest = req.unsafeEncode override def data: HttpData = req.data + override def toString: String = + s"ParameterizedRequest($req, $params)" } object ParameterizedRequest { diff --git a/zio-http/src/main/scala/zhttp/http/Response.scala b/zio-http/src/main/scala/zhttp/http/Response.scala index 40faed27a2..fa2b44fac8 100644 --- a/zio-http/src/main/scala/zhttp/http/Response.scala +++ b/zio-http/src/main/scala/zhttp/http/Response.scala @@ -6,10 +6,9 @@ import io.netty.handler.codec.http.{FullHttpResponse, HttpHeaderNames, HttpRespo import zhttp.html._ import zhttp.http.headers.HeaderExtension import zhttp.socket.{IsWebSocket, Socket, SocketApp} -import zio.{Chunk, UIO, ZIO} +import zio.{UIO, ZIO} import java.io.{PrintWriter, StringWriter} -import java.nio.charset.Charset final case class Response private ( status: Status, @@ -19,6 +18,47 @@ final case class Response private ( ) extends HeaderExtension[Response] with HttpDataExtension[Response] { self => + /** + * Encodes the Response into a Netty HttpResponse. Sets default headers such + * as `content-length`. For performance reasons, it is possible that it uses a + * FullHttpResponse if the complete data is available. Otherwise, it would + * create a DefaultHttpResponse without any content. + */ + private[zhttp] def unsafeEncode(): HttpResponse = { + import io.netty.handler.codec.http._ + + val jHeaders = self.headers.encode + val jContent = self.data match { + case HttpData.UnsafeAsync(_) => null + case data: HttpData.Complete => + data match { + case HttpData.FromAsciiString(text) => Unpooled.wrappedBuffer(text.array()) + case HttpData.BinaryChunk(data) => Unpooled.wrappedBuffer(data.toArray) + case HttpData.BinaryByteBuf(data) => data + case HttpData.BinaryStream(_) => null + case HttpData.Empty => Unpooled.EMPTY_BUFFER + case HttpData.JavaFile(_) => null + } + } + + val hasContentLength = jHeaders.contains(HttpHeaderNames.CONTENT_LENGTH) + if (jContent == null) { + // TODO: Unit test for this + // Client can't handle chunked responses and currently treats them as a FullHttpResponse. + // Due to this client limitation it is not possible to write a unit-test for this. + // Alternative would be to use sttp client for this use-case. + + if (!hasContentLength) jHeaders.set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED) + + new DefaultHttpResponse(HttpVersion.HTTP_1_1, self.status.asJava, jHeaders) + } else { + val jResponse = new DefaultFullHttpResponse(HTTP_1_1, self.status.asJava, jContent, false) + if (!hasContentLength) jHeaders.set(HttpHeaderNames.CONTENT_LENGTH, jContent.readableBytes()) + jResponse.headers().add(jHeaders) + jResponse + } + } + /** * Adds cookies in the response headers. */ @@ -49,6 +89,11 @@ final case class Response private ( def setStatus(status: Status): Response = self.copy(status = status) + /** + * Creates an Http from a Response + */ + def toHttp: Http[Any, Nothing, Any, Response] = Http.succeed(self) + /** * Updates the headers using the provided function */ @@ -59,50 +104,16 @@ final case class Response private ( * A more efficient way to append server-time to the response headers. */ def withServerTime: Response = self.copy(attribute = self.attribute.withServerTime) - - /** - * Encodes the Response into a Netty HttpResponse. Sets default headers such - * as `content-length`. For performance reasons, it is possible that it uses a - * FullHttpResponse if the complete data is available. Otherwise, it would - * create a DefaultHttpResponse without any content. - */ - private[zhttp] def unsafeEncode(): HttpResponse = { - import io.netty.handler.codec.http._ - - val jHeaders = self.headers.encode - val jContent = self.data match { - case HttpData.Incoming(_) => null - case data: HttpData.Outgoing => - data match { - case HttpData.Text(text, charset) => Unpooled.wrappedBuffer(text.getBytes(charset)) - case HttpData.BinaryChunk(data) => Unpooled.copiedBuffer(data.toArray) - case HttpData.BinaryByteBuf(data) => data - case HttpData.BinaryStream(_) => null - case HttpData.Empty => Unpooled.EMPTY_BUFFER - case HttpData.RandomAccessFile(_) => null - } - } - - val hasContentLength = jHeaders.contains(HttpHeaderNames.CONTENT_LENGTH) - if (jContent == null) { - // TODO: Unit test for this - // Client can't handle chunked responses and currently treats them as a FullHttpResponse. - // Due to this client limitation it is not possible to write a unit-test for this. - // Alternative would be to use sttp client for this use-case. - - if (!hasContentLength) jHeaders.set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED) - - new DefaultHttpResponse(HttpVersion.HTTP_1_1, self.status.asJava, jHeaders) - } else { - val jResponse = new DefaultFullHttpResponse(HTTP_1_1, self.status.asJava, jContent, false) - if (!hasContentLength) jHeaders.set(HttpHeaderNames.CONTENT_LENGTH, jContent.readableBytes()) - jResponse.headers().add(jHeaders) - jResponse - } - } } object Response { + private[zhttp] def unsafeFromJResponse(jRes: FullHttpResponse): Response = { + val status = Status.fromHttpResponseStatus(jRes.status()) + val headers = Headers.decode(jRes.headers()) + val data = HttpData.fromByteBuf(Unpooled.copiedBuffer(jRes.content())) + Response(status, headers, data) + } + def apply[R, E]( status: Status = Status.Ok, headers: Headers = Headers.empty, @@ -181,9 +192,9 @@ object Response { /** * Creates a response with content-type set to application/json */ - def json(data: String): Response = + def json(data: CharSequence): Response = Response( - data = HttpData.fromChunk(Chunk.fromArray(data.getBytes(HTTP_CHARSET))), + data = HttpData.fromCharSequence(data), headers = Headers(HeaderNames.contentType, HeaderValues.applicationJson), ) @@ -196,7 +207,7 @@ object Response { * Creates an empty response with status 301 or 302 depending on if it's * permanent or not. */ - def redirect(location: String, isPermanent: Boolean = false): Response = { + def redirect(location: CharSequence, isPermanent: Boolean = false): Response = { val status = if (isPermanent) Status.PermanentRedirect else Status.TemporaryRedirect Response(status, Headers.location(location)) } @@ -209,19 +220,12 @@ object Response { /** * Creates a response with content-type set to text/plain */ - def text(text: String, charset: Charset = HTTP_CHARSET): Response = + def text(text: CharSequence): Response = Response( - data = HttpData.fromString(text, charset), + data = HttpData.fromCharSequence(text), headers = Headers(HeaderNames.contentType, HeaderValues.textPlain), ) - private[zhttp] def unsafeFromJResponse(jRes: FullHttpResponse): Response = { - val status = Status.fromHttpResponseStatus(jRes.status()) - val headers = Headers.decode(jRes.headers()) - val data = HttpData.fromByteBuf(Unpooled.copiedBuffer(jRes.content())) - Response(status, headers, data) - } - /** * Attribute holds meta data for the backend */ diff --git a/zio-http/src/main/scala/zhttp/service/Handler.scala b/zio-http/src/main/scala/zhttp/service/Handler.scala index 73197a81f2..2a09265b67 100644 --- a/zio-http/src/main/scala/zhttp/service/Handler.scala +++ b/zio-http/src/main/scala/zhttp/service/Handler.scala @@ -72,7 +72,7 @@ private[zhttp] final case class Handler[R]( jReq, app, new Request { - override def data: HttpData = HttpData.Incoming(callback => + override def data: HttpData = HttpData.UnsafeAsync(callback => ctx .pipeline() .addAfter(HTTP_REQUEST_HANDLER, HTTP_CONTENT_HANDLER, new RequestBodyHandler(callback)): Unit, diff --git a/zio-http/src/main/scala/zhttp/service/HttpRuntime.scala b/zio-http/src/main/scala/zhttp/service/HttpRuntime.scala index 058e7dba4a..9092a36526 100644 --- a/zio-http/src/main/scala/zhttp/service/HttpRuntime.scala +++ b/zio-http/src/main/scala/zhttp/service/HttpRuntime.scala @@ -47,7 +47,7 @@ final class HttpRuntime[+R](strategy: HttpRuntime.Strategy[R]) { .unsafeRunAsyncWith(program) { case Exit.Success(_) => () case Exit.Failure(cause) => - cause.failureOption match { + cause.failureOption.orElse(cause.dieOption) match { case None => () case Some(_) => java.lang.System.err.println(cause.prettyPrint) } diff --git a/zio-http/src/main/scala/zhttp/service/server/content/handlers/ServerResponseHandler.scala b/zio-http/src/main/scala/zhttp/service/server/content/handlers/ServerResponseHandler.scala index 14286c178e..3ea49e322c 100644 --- a/zio-http/src/main/scala/zhttp/service/server/content/handlers/ServerResponseHandler.scala +++ b/zio-http/src/main/scala/zhttp/service/server/content/handlers/ServerResponseHandler.scala @@ -9,7 +9,7 @@ import zhttp.service.{ChannelFuture, HttpRuntime, Server} import zio.stream.ZStream import zio.{UIO, ZIO} -import java.io.RandomAccessFile +import java.io.File private[zhttp] trait ServerResponseHandler[R] { type Ctx = ChannelHandlerContext @@ -20,10 +20,20 @@ private[zhttp] trait ServerResponseHandler[R] { def writeResponse(msg: Response, jReq: HttpRequest)(implicit ctx: Ctx): Unit = { ctx.write(encodeResponse(msg)) - writeData(msg.data.asInstanceOf[HttpData.Outgoing], jReq) + writeData(msg.data.asInstanceOf[HttpData.Complete], jReq) () } + /** + * Enables auto-read if possible. Also performs the first read. + */ + private def attemptAutoRead()(implicit ctx: Ctx): Unit = { + if (!config.useAggregator && !ctx.channel().config().isAutoRead) { + ctx.channel().config().setAutoRead(true) + ctx.read(): Unit + } + } + /** * Checks if an encoded version of the response exists, uses it if it does. * Otherwise, it will return a fresh response. It will also set the server @@ -49,6 +59,16 @@ private[zhttp] trait ServerResponseHandler[R] { jResponse } + private def flushReleaseAndRead(jReq: HttpRequest)(implicit ctx: Ctx): Unit = { + ctx.flush() + releaseAndRead(jReq) + } + + private def releaseAndRead(jReq: HttpRequest)(implicit ctx: Ctx): Unit = { + releaseRequest(jReq) + attemptAutoRead() + } + /** * Releases the FullHttpRequest safely. */ @@ -62,10 +82,9 @@ private[zhttp] trait ServerResponseHandler[R] { /** * Writes file content to the Channel. Does not use Chunked transfer encoding */ - private def unsafeWriteFileContent(raf: RandomAccessFile)(implicit ctx: ChannelHandlerContext): Unit = { - val fileLength = raf.length() + private def unsafeWriteFileContent(file: File)(implicit ctx: ChannelHandlerContext): Unit = { // Write the content. - ctx.write(new DefaultFileRegion(raf.getChannel, 0, fileLength)) + ctx.write(new DefaultFileRegion(file, 0, file.length())) // Write the end marker. ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT): Unit } @@ -73,32 +92,25 @@ private[zhttp] trait ServerResponseHandler[R] { /** * Writes data on the channel */ - private def writeData(data: HttpData.Outgoing, jReq: HttpRequest)(implicit ctx: Ctx): Unit = { + private def writeData(data: HttpData.Complete, jReq: HttpRequest)(implicit ctx: Ctx): Unit = { data match { - case HttpData.BinaryStream(stream) => + + case _: HttpData.FromAsciiString => flushReleaseAndRead(jReq) + + case _: HttpData.BinaryChunk => flushReleaseAndRead(jReq) + + case _: HttpData.BinaryByteBuf => flushReleaseAndRead(jReq) + + case HttpData.Empty => flushReleaseAndRead(jReq) + + case HttpData.BinaryStream(stream) => rt.unsafeRun(ctx) { - writeStreamContent(stream).ensuring(UIO { - releaseRequest(jReq) - if (!config.useAggregator && !ctx.channel().config().isAutoRead) { - ctx.channel().config().setAutoRead(true) - ctx.read(): Unit - } // read next HttpContent - }) + writeStreamContent(stream).ensuring(UIO(releaseAndRead(jReq))) } - case HttpData.RandomAccessFile(raf) => - unsafeWriteFileContent(raf()) - releaseRequest(jReq) - if (!config.useAggregator && !ctx.channel().config().isAutoRead) { - ctx.channel().config().setAutoRead(true) - ctx.read(): Unit - } // read next HttpContent - case _ => - ctx.flush() - releaseRequest(jReq) - if (!config.useAggregator && !ctx.channel().config().isAutoRead) { - ctx.channel().config().setAutoRead(true) - ctx.read(): Unit - } // read next HttpContent + + case HttpData.JavaFile(unsafeGet) => + unsafeWriteFileContent(unsafeGet()) + releaseAndRead(jReq) } } diff --git a/zio-http/src/test/scala/zhttp/html/DomSpec.scala b/zio-http/src/test/scala/zhttp/html/DomSpec.scala index f4ac9aa85b..c1272cef63 100644 --- a/zio-http/src/test/scala/zhttp/html/DomSpec.scala +++ b/zio-http/src/test/scala/zhttp/html/DomSpec.scala @@ -69,8 +69,8 @@ object DomSpec extends DefaultRunnableSpec { assertTrue(dom.encode == """zio-http""") } + suite("Self Closing") { - val voidTagGen: Gen[Any, String] = Gen.fromIterable(Element.voidElementNames) - val tagGen: Gen[Random, String] = + val voidTagGen: Gen[Any, CharSequence] = Gen.fromIterable(Element.voidElementNames) + val tagGen: Gen[Random, String] = Gen.stringBounded(1, 5)(Gen.alphaChar).filterNot(Element.voidElementNames.contains) test("void") { diff --git a/zio-http/src/test/scala/zhttp/http/HttpDataSpec.scala b/zio-http/src/test/scala/zhttp/http/HttpDataSpec.scala index 6cd7d873e1..2005d29e8e 100644 --- a/zio-http/src/test/scala/zhttp/http/HttpDataSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/HttpDataSpec.scala @@ -1,29 +1,47 @@ package zhttp.http +import zhttp.http.HttpData.ByteBufConfig +import zio.durationInt import zio.stream.ZStream -import zio.test.Assertion.equalTo +import zio.test.Assertion.{anything, equalTo, isLeft, isSubtype} +import zio.test.TestAspect.timeout import zio.test.{DefaultRunnableSpec, Gen, assertM, checkAll} import java.io.File object HttpDataSpec extends DefaultRunnableSpec { - // TODO : Add tests for othe HttpData types override def spec = - suite("HttpDataSpec")( - suite("Test toByteBuf")( - test("HttpData.fromFile") { - val file = new File(getClass.getResource("/TestFile.txt").getPath) - val res = HttpData.fromFile(file).toByteBuf.map(_.toString(HTTP_CHARSET)) - assertM(res)(equalTo("abc\nfoo")) - }, - test("HttpData.fromStream") { - checkAll(Gen.string) { payload => - val stringBuffer = payload.toString.getBytes(HTTP_CHARSET) - val responseContent = ZStream.fromIterable(stringBuffer) - val res = HttpData.fromStream(responseContent).toByteBuf.map(_.toString(HTTP_CHARSET)) - assertM(res)(equalTo(payload)) - } - }, - ), - ) + suite("HttpDataSpec") { + val testFile = new File(getClass.getResource("/TestFile.txt").getPath) + suite("outgoing") { + suite("encode")( + suite("fromStream") { + test("success") { + checkAll(Gen.string) { payload => + val stringBuffer = payload.getBytes(HTTP_CHARSET) + val responseContent = ZStream.fromIterable(stringBuffer) + val res = HttpData.fromStream(responseContent).toByteBuf.map(_.toString(HTTP_CHARSET)) + assertM(res)(equalTo(payload)) + } + } + }, + suite("fromFile")( + test("failure") { + val res = HttpData.fromFile(throw new Error("Failure")).toByteBuf.either + assertM(res)(isLeft(isSubtype[Error](anything))) + }, + test("success") { + lazy val file = testFile + val res = HttpData.fromFile(file).toByteBuf.map(_.toString(HTTP_CHARSET)) + assertM(res)(equalTo("abc\nfoo")) + }, + test("success small chunk") { + lazy val file = testFile + val res = HttpData.fromFile(file).toByteBuf(ByteBufConfig(3)).map(_.toString(HTTP_CHARSET)) + assertM(res)(equalTo("abc\nfoo")) + }, + ), + ) + } + } @@ timeout(5 seconds) } diff --git a/zio-http/src/test/scala/zhttp/http/HttpSpec.scala b/zio-http/src/test/scala/zhttp/http/HttpSpec.scala index 3f5fe101ad..bf101cdaa9 100644 --- a/zio-http/src/test/scala/zhttp/http/HttpSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/HttpSpec.scala @@ -79,6 +79,25 @@ object HttpSpec extends DefaultRunnableSpec with HExitAssertion { assert(actual)(isEmpty) }, ) + + suite("codecMiddleware")( + test("codec success") { + val a = Http.collect[Int] { case v => v.toString } + val b = Http.collect[String] { case v => v.toInt } + val app = Http.identity[String] @@ (a \/ b) + val actual = app.execute(2) + assert(actual)(isSuccess(equalTo(2))) + } + + test("encoder failure") { + val app = Http.identity[Int] @@ (Http.succeed(1) \/ Http.fail("fail")) + val actual = app.execute(()) + assert(actual)(isFailure(equalTo("fail"))) + } + + test("decoder failure") { + val app = Http.identity[Int] @@ (Http.fail("fail") \/ Http.succeed(1)) + val actual = app.execute(()) + assert(actual)(isFailure(equalTo("fail"))) + }, + ) + suite("collectHExit")( test("should succeed") { val a = Http.collectHExit[Int] { case 1 => HExit.succeed("OK") } diff --git a/zio-http/src/test/scala/zhttp/http/MiddlewareSpec.scala b/zio-http/src/test/scala/zhttp/http/MiddlewareSpec.scala index f902c52e6d..75b67c7fd6 100644 --- a/zio-http/src/test/scala/zhttp/http/MiddlewareSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/MiddlewareSpec.scala @@ -150,6 +150,25 @@ object MiddlewareSpec extends DefaultRunnableSpec with HExitAssertion { val app = Http.identity[Int] @@ mid assertM(app("1").exit)(fails(anything)) } + } + + suite("codecHttp") { + test("codec success") { + val a = Http.collect[Int] { case v => v.toString } + val b = Http.collect[String] { case v => v.toInt } + val mid = Middleware.codecHttp[String, Int](b, a) + val app = Http.identity[Int] @@ mid + assertM(app("2"))(equalTo("2")) + } + + test("encoder failure") { + val mid = Middleware.codecHttp[String, Int](Http.succeed(1), Http.fail("fail")) + val app = Http.identity[Int] @@ mid + assertM(app("2").exit)(fails(anything)) + } + + test("decoder failure") { + val mid = Middleware.codecHttp[String, Int](Http.fail("fail"), Http.succeed(2)) + val app = Http.identity[Int] @@ mid + assertM(app("2").exit)(fails(anything)) + } } } } diff --git a/zio-http/src/test/scala/zhttp/http/RequestSpec.scala b/zio-http/src/test/scala/zhttp/http/RequestSpec.scala new file mode 100644 index 0000000000..0820ca5723 --- /dev/null +++ b/zio-http/src/test/scala/zhttp/http/RequestSpec.scala @@ -0,0 +1,28 @@ +package zhttp.http + +import zhttp.internal.HttpGen +import zio.test.Assertion._ +import zio.test._ + +object RequestSpec extends DefaultRunnableSpec { + def spec = suite("Request")( + suite("toString") { + test("should produce string representation of a request") { + check(HttpGen.request) { req => + assert(req.toString)( + equalTo(s"Request(${req.version}, ${req.method}, ${req.url}, ${req.headers}, ${req.remoteAddress})"), + ) + } + } + + test("should produce string representation of a parameterized request") { + check(HttpGen.parameterizedRequest(Gen.alphaNumericString)) { req => + assert(req.toString)( + equalTo( + s"ParameterizedRequest(Request(${req.version}, ${req.method}, ${req.url}, ${req.headers}, ${req.remoteAddress}), ${req.params})", + ), + ) + } + } + }, + ) +} diff --git a/zio-http/src/test/scala/zhttp/http/ResponseSpec.scala b/zio-http/src/test/scala/zhttp/http/ResponseSpec.scala index 8b13789179..4bf3c41667 100644 --- a/zio-http/src/test/scala/zhttp/http/ResponseSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/ResponseSpec.scala @@ -1 +1,41 @@ +package zhttp.http +import zio.test.Assertion._ +import zio.test._ + +object ResponseSpec extends DefaultRunnableSpec { + def spec = suite("Response")( + suite("redirect") { + val location = "www.google.com" + test("Temporary redirect should produce a response with a TEMPORARY_REDIRECT") { + val x = Response.redirect(location) + assertTrue(x.status == Status.TemporaryRedirect) && + assertTrue(x.headerValue(HeaderNames.location).contains(location)) + } + + test("Temporary redirect should produce a response with a location") { + val x = Response.redirect(location) + assertTrue(x.headerValue(HeaderNames.location).contains(location)) + } + + test("Permanent redirect should produce a response with a PERMANENT_REDIRECT") { + val x = Response.redirect(location, true) + assertTrue(x.status == Status.PermanentRedirect) + } + + test("Permanent redirect should produce a response with a location") { + val x = Response.redirect(location, true) + assertTrue(x.headerValue(HeaderNames.location).contains(location)) + } + } + + suite("json")( + test("Json should set content type to ApplicationJson") { + val x = Response.json("""{"message": "Hello"}""") + assertTrue(x.headerValue(HeaderNames.contentType).contains(HeaderValues.applicationJson.toString)) + }, + ) + + suite("toHttp")( + test("should convert response to Http") { + val http = Http(Response.ok) + assertM(http(()))(equalTo(Response.ok)) + }, + ), + ) +} diff --git a/zio-http/src/test/scala/zhttp/internal/DynamicServer.scala b/zio-http/src/test/scala/zhttp/internal/DynamicServer.scala index af786af887..4c13302ad5 100644 --- a/zio-http/src/test/scala/zhttp/internal/DynamicServer.scala +++ b/zio-http/src/test/scala/zhttp/internal/DynamicServer.scala @@ -1,31 +1,19 @@ package zhttp.internal import zhttp.http._ -import zhttp.internal.DynamicServer.{HttpEnv, Id} import zhttp.service.Server.Start import zio._ import java.util.UUID -sealed trait DynamicServer { - def add(app: HttpApp[HttpEnv, Throwable]): UIO[Id] - def get(id: Id): UIO[Option[HttpApp[HttpEnv, Throwable]]] +object DynamicServer { - def port: ZIO[Any, Nothing, Int] + type Id = String - def start: IO[Nothing, Start] - - def setStart(n: Start): UIO[Boolean] -} -object DynamicServer { - - type Id = String - type HttpEnv = DynamicServer with Console - type HttpAppTest = HttpApp[HttpEnv, Throwable] val APP_ID = "X-APP_ID" - def app: HttpApp[HttpEnv, Throwable] = Http - .fromOptionFunction[Request] { case req => + def app: HttpApp[DynamicServer, Throwable] = Http + .fromOptionFunction[Request] { req => for { id <- req.headerValue(APP_ID) match { case Some(id) => UIO(id) @@ -42,52 +30,57 @@ object DynamicServer { def baseURL(scheme: Scheme): ZIO[DynamicServer, Nothing, String] = port.map(port => s"${scheme.encode}://localhost:$port") - def deploy(app: HttpApp[HttpEnv, Throwable]): ZIO[DynamicServer, Nothing, String] = - ZIO.serviceWithZIO[DynamicServer](_.add(app)) + def deploy[R](app: HttpApp[R, Throwable]): ZIO[DynamicServer with R, Nothing, String] = + for { + env <- ZIO.environment[R] + id <- ZIO.environmentWithZIO[DynamicServer](_.get.add(app.provideEnvironment(env))) + } yield id - def get(id: Id): ZIO[DynamicServer, Nothing, Option[HttpApp[HttpEnv, Throwable]]] = - ZIO.serviceWithZIO[DynamicServer](_.get(id)) + def get(id: Id): ZIO[DynamicServer, Nothing, Option[HttpApp[Any, Throwable]]] = + ZIO.environmentWithZIO[DynamicServer](_.get.get(id)) def httpURL: ZIO[DynamicServer, Nothing, String] = baseURL(Scheme.HTTP) def live: ZLayer[Any, Nothing, DynamicServer] = { for { - ref <- Ref.make(Map.empty[Id, HttpApp[HttpEnv, Throwable]]) + ref <- Ref.make(Map.empty[Id, HttpApp[Any, Throwable]]) pr <- Promise.make[Nothing, Start] } yield new Live(ref, pr) }.toLayer - def port: ZIO[DynamicServer, Nothing, Int] = ZIO.serviceWithZIO[DynamicServer](_.port) + def port: ZIO[DynamicServer, Nothing, Int] = ZIO.environmentWithZIO[DynamicServer](_.get.port) - def setStart(s: Start): ZIO[DynamicServer, Nothing, Boolean] = ZIO.serviceWithZIO[DynamicServer](_.setStart(s)) + def setStart(s: Start): ZIO[DynamicServer, Nothing, Boolean] = + ZIO.environmentWithZIO[DynamicServer](_.get.setStart(s)) - def start: ZIO[DynamicServer, Nothing, Start] = ZIO.serviceWithZIO[DynamicServer](_.start) + def start: ZIO[DynamicServer, Nothing, Start] = ZIO.environmentWithZIO[DynamicServer](_.get.start) def wsURL: ZIO[DynamicServer, Nothing, String] = baseURL(Scheme.WS) sealed trait Service { - def add(app: HttpApp[HttpEnv, Throwable]): UIO[Id] - def get(id: Id): UIO[Option[HttpApp[HttpEnv, Throwable]]] + def add(app: HttpApp[Any, Throwable]): UIO[Id] - def port: ZIO[Any, Nothing, Int] + def get(id: Id): UIO[Option[HttpApp[Any, Throwable]]] - def start: IO[Nothing, Start] + def port: ZIO[Any, Nothing, Int] def setStart(n: Start): UIO[Boolean] + + def start: IO[Nothing, Start] } - final class Live(ref: Ref[Map[Id, HttpApp[HttpEnv, Throwable]]], pr: Promise[Nothing, Start]) extends DynamicServer { - def add(app: HttpApp[HttpEnv, Throwable]): UIO[Id] = for { + final class Live(ref: Ref[Map[Id, HttpApp[Any, Throwable]]], pr: Promise[Nothing, Start]) extends Service { + def add(app: HttpApp[Any, Throwable]): UIO[Id] = for { id <- UIO(UUID.randomUUID().toString) _ <- ref.update(map => map + (id -> app)) } yield id - def get(id: Id): UIO[Option[HttpApp[HttpEnv, Throwable]]] = ref.get.map(_.get(id)) + def get(id: Id): UIO[Option[HttpApp[Any, Throwable]]] = ref.get.map(_.get(id)) def port: ZIO[Any, Nothing, Int] = start.map(_.port) - def start: IO[Nothing, Start] = pr.await - def setStart(s: Start): UIO[Boolean] = pr.complete(ZIO(s).orDie) + + def start: IO[Nothing, Start] = pr.await } } diff --git a/zio-http/src/test/scala/zhttp/internal/HttpGen.scala b/zio-http/src/test/scala/zhttp/internal/HttpGen.scala index 41ceb6d8fb..d3b5c99db1 100644 --- a/zio-http/src/test/scala/zhttp/internal/HttpGen.scala +++ b/zio-http/src/test/scala/zhttp/internal/HttpGen.scala @@ -1,12 +1,13 @@ package zhttp.internal import io.netty.buffer.Unpooled +import zhttp.http.Request.ParameterizedRequest import zhttp.http.Scheme.{HTTP, HTTPS, WS, WSS} import zhttp.http.URL.Location import zhttp.http._ import zio.stream.ZStream import zio.test.{Gen, Sized} -import zio.{Chunk, ZIO, _} +import zio.{Chunk, Random, ZIO} import java.io.File @@ -124,6 +125,13 @@ object HttpGen { } yield p } + def parameterizedRequest[R, A](paramsGen: Gen[R, A]): Gen[R with Random with Sized, ParameterizedRequest[A]] = { + for { + req <- request + params <- paramsGen + } yield ParameterizedRequest(req, params) + } + def request: Gen[Random with Sized, Request] = for { version <- httpVersion method <- HttpGen.method diff --git a/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala b/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala index bb5d5e2679..0809a114c2 100644 --- a/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala +++ b/zio-http/src/test/scala/zhttp/internal/HttpRunnableSpec.scala @@ -2,8 +2,6 @@ package zhttp.internal import zhttp.http.URL.Location import zhttp.http._ -import zhttp.internal.DynamicServer.HttpEnv -import zhttp.internal.HttpRunnableSpec.HttpTestClient import zhttp.service.Client.Config import zhttp.service._ import zhttp.service.client.ClientSSLHandler.ClientSSLOptions @@ -47,7 +45,10 @@ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => } } - implicit class RunnableHttpClientAppSyntax(app: HttpApp[HttpEnv, Throwable]) { + implicit class RunnableHttpClientAppSyntax[R, E](http: HttpApp[R, E]) { + + def app(implicit e: E <:< Throwable): HttpApp[R, Throwable] = + http.asInstanceOf[HttpApp[R, Throwable]] /** * Deploys the http application on the test server and returns a Http of @@ -56,7 +57,7 @@ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => * while writing tests. It also allows us to simply pass a request in the * end, to execute, and resolve it with a response, like a normal HttpApp. */ - def deploy: HttpTestClient[Any, Request, Response] = + def deploy(implicit e: E <:< Throwable): Http[R with HttpEnv, Throwable, Request, Response] = for { port <- Http.fromZIO(DynamicServer.port) id <- Http.fromZIO(DynamicServer.deploy(app)) @@ -70,11 +71,11 @@ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => } } yield response - def deployWS: HttpTestClient[Any, SocketApp[Any], Response] = + def deployWS(implicit e: E <:< Throwable): Http[R with HttpEnv, Throwable, SocketApp[HttpEnv], Response] = for { id <- Http.fromZIO(DynamicServer.deploy(app)) url <- Http.fromZIO(DynamicServer.wsURL) - response <- Http.fromFunctionZIO[SocketApp[Any]] { app => + response <- Http.fromFunctionZIO[SocketApp[HttpEnv]] { app => Client.socket( url = url, headers = Headers(DynamicServer.APP_ID, id), @@ -111,13 +112,3 @@ abstract class HttpRunnableSpec extends DefaultRunnableSpec { self => } yield status } } - -object HttpRunnableSpec { - type HttpTestClient[-R, -A, +B] = - Http[ - R with EventLoopGroup with ChannelFactory with DynamicServer with ServerChannelFactory, - Throwable, - A, - B, - ] -} diff --git a/zio-http/src/test/scala/zhttp/internal/package.scala b/zio-http/src/test/scala/zhttp/internal/package.scala new file mode 100644 index 0000000000..d889c50934 --- /dev/null +++ b/zio-http/src/test/scala/zhttp/internal/package.scala @@ -0,0 +1,8 @@ +package zhttp + +import zhttp.service.{ChannelFactory, EventLoopGroup, ServerChannelFactory} + +package object internal { + type DynamicServer = DynamicServer.Service + type HttpEnv = EventLoopGroup with ChannelFactory with DynamicServer with ServerChannelFactory +}