diff --git a/.github/agents/dotnet-agent.md b/.github/agents/dotnet-agent.md new file mode 100644 index 0000000..7204b48 --- /dev/null +++ b/.github/agents/dotnet-agent.md @@ -0,0 +1,12 @@ +--- +name: dotnet expert +description: Build amazing things in C# +--- + +# My Agent + +Designed to provide guidance and best practices for C# development and build http client tool likes httpie, this agent brings deep expertise in: + +- Core C# Development: Adheres to modern best practices for syntax, structure, and performance, while still honoring the preferences of the existing repository. +- Code Integrity: Implements minimal code changes and writes efficient code utilizing async/await patterns with proper cancellation and exception handling. +- Testing Best Practices: Supports behavior-driven unit testing practices, integration testing, and TDD workflows. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..3cdc3c1 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,138 @@ +# dotnet-httpie + +A command-line HTTP client for .NET, providing a user-friendly alternative to curl for API testing and debugging. This tool can be installed as a global .NET tool or run as a Docker container. + +Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here. + +## Working Effectively + +### Build and Test Process + +- **NEVER CANCEL builds or tests** - Builds and tests typically complete quickly. Set timeout to 60+ seconds. +- **Recommended Build Method**: `./build.sh` (uses dotnet-execute, builds and runs tests automatically) +- **Alternative Build Methods**: + ```bash + dotnet build # Direct dotnet CLI + dotnet build dotnet-httpie.slnx # Specify solution file explicitly + ``` +- **Individual Test Commands**: + ```bash + dotnet test tests/HTTPie.UnitTest/HTTPie.UnitTest.csproj # Unit tests + dotnet test tests/HTTPie.IntegrationTest/HTTPie.IntegrationTest.csproj # Integration tests + ``` +- **Package the tool**: + ```bash + dotnet pack src/HTTPie/HTTPie.csproj --configuration Release + ``` + +### Running the Application + +- Run in development: + ```bash + dotnet run --project src/HTTPie/HTTPie.csproj --framework net10.0 -- --help + ``` + +- Install as global tool: + ```bash + dotnet tool install --global --add-source src/HTTPie/bin/Release dotnet-httpie + dotnet-http --help + ``` + +- Test basic functionality: + ```bash + # Test in offline mode (no network required) + dotnet-http https://httpbin.org/get --offline + + # Execute .http files (test assets available) + dotnet-http exec tests/HTTPie.IntegrationTest/TestAssets/HttpStartedSample.http --offline + ``` + +## Validation + +- **ALWAYS run complete build and test suite** before submitting changes +- Build validation commands that MUST pass: + ```bash + dotnet build # build + dotnet test # run test cases + dotnet pack src/HTTPie/HTTPie.csproj # pack artifacts + ``` +- **MANUAL VALIDATION SCENARIOS**: After code changes, test these workflows: + 1. **CLI Help**: `dotnet-http --help` - verify all options display correctly + 2. **HTTP Request**: `dotnet-http https://httpbin.org/get --offline` - verify request formatting + 3. **HTTP File Execution**: `dotnet-http exec tests/HTTPie.IntegrationTest/TestAssets/HttpStartedSample.http --offline` - verify .http file parsing + 4. **Package Installation**: Install as global tool and verify `dotnet-http` command works +- Integration tests should pass when network connectivity is available + +## Key Project Structure + +### Repository Layout +``` +/ +├── .github/workflows/ # CI/CD pipelines (dotnet.yml is main build) +├── src/HTTPie/ # Main application project (multi-targets net8.0;net10.0) +├── tests/ # Test projects +│ ├── HTTPie.UnitTest/ # Unit tests (all should pass) +│ └── HTTPie.IntegrationTest/ # Integration tests +├── build/ # Build scripts and dotnet-execute configuration +├── docs/ # Release notes and documentation +├── dotnet-httpie.slnx # Solution file (requires .NET 10 SDK) +├── build.sh # Build script using dotnet-execute +└── .husky/ # Git hooks (pre-commit runs dotnet build) +``` + +### Important Files +- `src/HTTPie/HTTPie.csproj` - Main project, packaged as global tool (`dotnet-http` command) +- `tests/HTTPie.IntegrationTest/TestAssets/` - Sample .http files for testing exec functionality +- `build/build.cs` - Build script executed by dotnet-execute tool +- `Directory.Build.props` - Common MSBuild properties (sets LangVersion to preview) + +### Technical Details +- **Target Frameworks**: net8.0 and net10.0 (multi-targeting enabled) +- **Package Output**: `src/HTTPie/bin/Release/dotnet-httpie.{version}.nupkg` +- **AOT Compilation**: Enabled for Release builds on .NET 10 (PublishAot=true) + +## Common Development Tasks + +### Complete Development Workflow +```bash +# 1. Build and test (recommended - runs both build and tests) +./build.sh + +# Alternative: Build and test separately +# dotnet build +# dotnet test + +# 2. Test functionality +dotnet run --project src/HTTPie/HTTPie.csproj --framework net10.0 -- --help +dotnet run --project src/HTTPie/HTTPie.csproj --framework net10.0 -- https://httpbin.org/get --offline + +# 3. Package and install +dotnet pack src/HTTPie/HTTPie.csproj --configuration Release +dotnet tool install --global --add-source src/HTTPie/bin/Release dotnet-httpie --version {version} +``` + +### Environment Setup Verification +```bash +# Verify .NET installation +dotnet --list-sdks # Should show 10.0.x +dotnet --list-runtimes # Should show Microsoft.NETCore.App 10.0.x + +# Verify tools +dotnet-exec --info # Should show dotnet-execute tool info +which dotnet-http # Should show path after global tool install +``` + +## Troubleshooting + +### Git Workflow +- Pre-commit hook automatically runs `dotnet build` +- Always ensure builds pass before pushing +- CI/CD runs on macOS, Linux, and Windows with .NET 10 SDK + +### Common Issues +- **Build fails with "unrecognized Solution element"**: Install .NET 10 SDK, .NET 8 doesn't support .slnx format +- **Tests fail with "Framework not found"**: Ensure .NET 10 runtime is installed and DOTNET_ROOT is set +- **dotnet-execute build script fails**: The tool has compatibility issues with .NET 10 preview, use direct `dotnet build` instead +- **Integration tests fail**: Ensure network connectivity is available for external service calls + +This project is a modern .NET tool that showcases advanced features like multi-targeting, AOT compilation, and global tool packaging. Always test the complete user workflow after making changes. diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000..19a8e94 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,36 @@ +name: "Copilot Setup Steps" + +# Automatically run the setup steps when they are changed to allow for easy validation, and +# allow manual testing through the repository's "Actions" tab +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: ubuntu-latest + + # Set the permissions to the lowest permissions possible needed for your steps. + # Copilot will be given its own token for its operations. + permissions: + # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. + contents: read + + # You can define any steps you want, and they will run before the agent starts. + # If you do not check out your code, Copilot will do this for you. + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.0.x + 9.0.x + 10.0.x diff --git a/.github/workflows/dotnet-outdated.yml b/.github/workflows/dotnet-outdated.yml index 47d4fd1..c4ef1a3 100644 --- a/.github/workflows/dotnet-outdated.yml +++ b/.github/workflows/dotnet-outdated.yml @@ -40,13 +40,12 @@ jobs: fi - name: Create Pull Request if: ${{ env.has_changes == 'true' }} - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: "Update NuGet Packages" title: 'Update NuGet Packages' body: > This PR updates the outdated NuGet packages. - labels: automated pr + labels: dependencies branch: update-nuget-dependencies - base: ${{ github.ref }} diff --git a/Directory.Packages.props b/Directory.Packages.props index 1512603..89caa2b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,15 +7,15 @@ - - - + + + - + - - + + - \ No newline at end of file + diff --git a/README.md b/README.md index c61b6a1..c5356ca 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,331 @@ # dotnet-HTTPie [![dotnet-HTTPie](https://img.shields.io/nuget/v/dotnet-httpie)](https://www.nuget.org/packages/dotnet-httpie/) - [![dotnet-HTTPie Latest](https://img.shields.io/nuget/vpre/dotnet-httpie)](https://www.nuget.org/packages/dotnet-httpie/absoluteLatest) - [![GitHub Action Build Status](https://github.com/WeihanLi/dotnet-httpie/actions/workflows/dotnet.yml/badge.svg)](https://github.com/WeihanLi/dotnet-httpie/actions/workflows/dotnet.yml) - [![Docker Pulls](https://img.shields.io/docker/pulls/weihanli/dotnet-httpie)](https://hub.docker.com/r/weihanli/dotnet-httpie/tags) - [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/WeihanLi/dotnet-httpie) -## Intro +> **Modern, user-friendly command-line HTTP client for the .NET ecosystem** -dotnet tool version of [httpie](https://github.com/httpie/httpie), Modern, user-friendly command-line HTTP client for the API era. +dotnet-httpie is a .NET tool that brings the power and simplicity of [HTTPie](https://github.com/httpie/httpie) to .NET developers. It's designed for testing, debugging, and interacting with APIs and HTTP servers with an intuitive command-line interface. ![httpie](https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-animation.gif) -HTTPie (pronounced aitch-tee-tee-pie) is a command-line HTTP client. Its goal is to make CLI interaction with web services as human-friendly as possible. HTTPie is designed for testing, debugging, and generally interacting with APIs & HTTP servers. +## ✨ Key Features -## Install +- 🚀 **Simple & Intuitive**: Human-friendly syntax for HTTP requests +- 📁 **File Execution**: Run `.http` and `.rest` files for repeatable testing +- 🔄 **cURL Support**: Execute cURL commands directly +- 🐳 **Docker Ready**: Available as a Docker image for containerized environments +- 🔗 **Request Chaining**: Reference previous responses in subsequent requests +- 🌍 **Environment Support**: Multiple environment configurations +- 📊 **Load Testing**: Built-in load testing capabilities +- 🔐 **Authentication**: Support for various auth methods (JWT, API keys, Basic auth) +- ⬇️ **File Downloads**: Download files with progress indicators +- 🔍 **JSON Schema Validation**: Validate API responses against schemas +- 💾 **Request Caching**: Cache requests for improved performance +- 🎯 **Middleware System**: Extensible request/response pipeline -Install .NET SDK before you get started with this tool, when you had .NET SDK installed, run the command below to install the latest stable version tool +## 🚀 Quick Start -``` bash +### Installation + +Install the latest stable version: + +```bash dotnet tool update --global dotnet-httpie ``` -For latest preview version, run the following command instead: +Or install the latest preview: -``` bash +```bash dotnet tool update --global dotnet-httpie --prerelease ``` -## GetStarted +### Your First Request + +```bash +# Simple GET request +dotnet-http httpbin.org/get + +# POST with JSON data +dotnet-http POST httpbin.org/post name=John age:=30 + +# With custom headers +dotnet-http GET httpbin.org/headers Authorization:"Bearer token" +``` + +## 📖 Documentation + +| Topic | Description | +|-------|-------------| +| 📋 [Installation Guide](docs/articles/installation.md) | Detailed installation instructions for all platforms | +| ⚡ [Quick Start](docs/articles/quick-start.md) | Get up and running in minutes | +| 🌐 [HTTP Requests](docs/articles/http-requests.md) | Complete guide to making HTTP requests | +| 📄 [File Execution](docs/articles/file-execution.md) | Execute .http/.rest files | +| 🐳 [Docker Usage](docs/articles/docker-usage.md) | Using dotnet-httpie with Docker | +| 💡 [Common Use Cases](docs/articles/examples/common-use-cases.md) | Practical examples and patterns | +| 🔧 [Full Documentation](docs/articles/README.md) | Complete documentation index | + +## 💫 Command Syntax + +```bash +dotnet-http [flags] [METHOD] URL [ITEM [ITEM]] +``` -Now you can use the tool to call your API you want +### Request Items -Usages: +| Type | Syntax | Example | Description | +|------|--------|---------|-------------| +| **Query Parameters** | `name==value` | `search==httpie` | URL query string parameters | +| **Headers** | `name:value` | `Authorization:Bearer token` | HTTP request headers | +| **JSON Data** | `name=value` | `name=John` | JSON request body fields | +| **Raw JSON** | `name:=value` | `age:=30`, `active:=true` | Raw JSON values (numbers, booleans, objects) | -> dotnet-http [flags] [METHOD] URL [ITEM [ITEM]] +## 🎯 Examples -There're three types of item +### Basic Requests -Type | Grammar ------|------- -Query| name`==`test -Header| X-Api-Key`:`test -Request-Data | name`=`test, raw data field example(Only effective for JSON): age`:=`10, job`:=`'{"Id":1,"Name":"test"}'(Escape may needed for Windows cmd or Windows PowerShell) +```bash +# GET request with query parameters +dotnet-http GET httpbin.org/get search==httpie limit==10 -Here's a sample: +# POST request with JSON data +dotnet-http POST httpbin.org/post name=John email=john@example.com age:=30 -``` sh -dotnet-http https://reservation.weihanli.xyz/health test==1234 name=test age:=10 flag:=true job:='{"id": 1, "name": "test"}' api-key:Abc12345 --offline +# PUT request with headers +dotnet-http PUT api.example.com/users/123 \ + Authorization:"Bearer token" \ + name="John Smith" \ + active:=true ``` -![sample](./images/sample.png) +### Advanced Usage -More examples you may wanna have a look +```bash +# Complex JSON with nested objects +dotnet-http POST api.example.com/users \ + name=John \ + address[city]=Seattle \ + address[zipcode]:=98101 \ + tags:='["developer", "api"]' -``` bash -dotnet-http :5000/api/values -dotnet-http localhost:5000/api/values -dotnet-http get https://reservation.weihanli.xyz/api/notice --body -dotnet-http /api/notice title=test body=test-body -dotnet-http post http://localhost/api/notice title=test body=test-body + + +# Download files +dotnet-http GET api.example.com/files/report.pdf --download +``` + +### Real-World API Examples + +```bash +# GitHub API +dotnet-http GET api.github.com/users/octocat + +# Create GitHub issue (with authentication) +dotnet-http POST api.github.com/repos/owner/repo/issues \ + Authorization:"token your-token" \ + title="Bug report" \ + body="Description of the issue" + +# JSON API with multiple data types +dotnet-http POST api.example.com/orders \ + Authorization:"Bearer jwt-token" \ + customer_id:=12345 \ + items:='[{"id": 1, "qty": 2}, {"id": 2, "qty": 1}]' \ + urgent:=true \ + notes="Please handle with care" ``` -## Execute +## 📁 File Execution -You can execute `*.http`/`*.rest` http requests with the `exec` command +Execute HTTP requests from `.http` and `.rest` files: -``` sh -dotnet-http exec HttpStartedSample.http +```bash +# Execute HTTP file +dotnet-http exec requests.http -dotnet-http exec ".\tests\HTTPie.IntegrationTest\TestAssets\HttpStartedSample.http" -dotnet-http exec ".\tests\HTTPie.IntegrationTest\TestAssets\HttpVariableSample.http" -dotnet-http exec ".\tests\HTTPie.IntegrationTest\TestAssets\HttpRequestReferenceSample.http" +# Execute with specific environment +dotnet-http exec api-tests.http --env production + +# Execute cURL commands +dotnet-http exec commands.curl --type curl ``` -see http request sample here: +### Example .http file: + +```http +@baseUrl = https://api.example.com +@token = your-jwt-token -## Docker +### -There's a docker image(weihanli/dotnet-httpie) that you could use directly without installing the tool, use sample: +# @name getUsers +GET {{baseUrl}}/users +Authorization: Bearer {{token}} -``` bash -docker run --rm --pull=always weihanli/dotnet-httpie:latest -v github.com +### -docker run --rm --pull=always weihanli/dotnet-httpie:latest reservation.weihanli.xyz/health job:='{"id":1,"name":"tester"}' --offline +# @name createUser +POST {{baseUrl}}/users +Authorization: Bearer {{token}} +Content-Type: application/json -docker run --rm --pull=always weihanli/dotnet-httpie:latest PUT httpbin.org hello=world +{ + "name": "John Doe", + "email": "john@example.com" +} -docker run --rm --pull=always weihanli/dotnet-httpie:latest get httpbin.org/status/400 +### + +# Reference previous response +GET {{baseUrl}}/users/{{createUser.response.body.id}} +Authorization: Bearer {{token}} ``` -## More +### Environment Support + +Create `http-client.env.json`: + +```json +{ + "development": { + "baseUrl": "http://localhost:3000", + "token": "dev-token" + }, + "production": { + "baseUrl": "https://api.example.com", + "token": "prod-token" + } +} +``` + +## 🐳 Docker + +Use dotnet-httpie without installing .NET: + +```bash +# Basic usage +docker run --rm weihanli/dotnet-httpie:latest httpbin.org/get + +# POST with data +docker run --rm weihanli/dotnet-httpie:latest POST httpbin.org/post name=test + +# Execute HTTP files +docker run --rm -v $(pwd):/workspace -w /workspace \ + weihanli/dotnet-httpie:latest exec requests.http + +# With environment variables +docker run --rm -e API_TOKEN="your-token" \ + weihanli/dotnet-httpie:latest GET api.example.com/protected \ + Authorization:"Bearer $API_TOKEN" +``` + +### Create an alias for easier usage: + +```bash +# Add to your shell profile (.bashrc, .zshrc, etc.) +alias http='docker run --rm weihanli/dotnet-httpie:latest' + +# Now use it like the installed version +http GET httpbin.org/get +http POST httpbin.org/post name=John +``` + +## 🔧 Advanced Features + +### Authentication +- **JWT Tokens**: `Authorization:"Bearer token"` +- **API Keys**: `X-API-Key:"key"` or `api_key==key` +- **Basic Auth**: `--auth username:password` or `Authorization:"Basic base64"` + +### File Operations +- **Form data**: `--form field=value` +- **Download**: `--download` flag +- **Send raw data**: `--raw "data"` + +### Request Features +- **Query parameters**: `param==value` +- **Custom headers**: `Header-Name:"value"` +- **JSON data**: `field=value` or `field:=rawjson` +- **Form data**: `--form` flag +- **Raw data**: `--raw "data"` + +### Execution Modes +- **Offline mode**: `--offline` (preview requests) +- **Debug mode**: `--debug` (detailed logging) +- **Environment**: `--env production` + +### Response Handling +- **Body only**: `--body` flag +- **Follow redirects**: `--follow` +- **JSON processing**: Pipe to `jq` for advanced processing + +## 🚀 Use Cases + +- **API Development**: Test endpoints during development +- **API Documentation**: Executable examples in documentation +- **CI/CD Testing**: Automated API testing in pipelines +- **Load Testing**: Built-in load testing capabilities +- **Integration Testing**: Test service-to-service communication +- **Debugging**: Inspect HTTP requests and responses +- **Scripting**: Automate API interactions in shell scripts + +## 🤝 Contributing + +We welcome contributions! Here's how you can help: + +1. **Report Issues**: Found a bug? [Open an issue](https://github.com/WeihanLi/dotnet-httpie/issues) +2. **Feature Requests**: Have an idea? [Open an issue](https://github.com/WeihanLi/dotnet-httpie/issues) +3. **Documentation**: Help improve the docs +4. **Code**: Submit pull requests for bug fixes or features + +### Development Setup + +```bash +# Clone the repository +git clone https://github.com/WeihanLi/dotnet-httpie.git +cd dotnet-httpie + +# Build the project +dotnet build + +# Run tests +dotnet test + +# Install locally for testing +dotnet pack +dotnet tool install --global --add-source ./artifacts dotnet-httpie +``` + +## 📚 Resources + +- **📖 [Complete Documentation](docs/articles/README.md)** - Comprehensive guides and tutorials +- **🎯 [Examples](docs/articles/examples/common-use-cases.md)** - Real-world usage patterns +- **🐳 [Docker Guide](docs/articles/docker-usage.md)** - Containerized usage +- **📄 [Release Notes](docs/ReleaseNotes.md)** - What's new in each version +- **💬 [Issues](https://github.com/WeihanLi/dotnet-httpie/issues)** - Community Q&A, bug reports, and feature requests + +## 🙏 Acknowledgments + +- Inspired by the amazing [HTTPie](https://github.com/httpie/httpie) project +- Built with ❤️ for the .NET community +- Special thanks to all [contributors](https://github.com/WeihanLi/dotnet-httpie/contributors) + +## 📄 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +--- + +
-For detailed document: have a look at HTTPie documents +**⭐ Star this repository if you find it useful!** -## References +[🏠 Homepage](https://github.com/WeihanLi/dotnet-httpie) • +[📖 Documentation](docs/articles/README.md) • +[🐳 Docker Hub](https://hub.docker.com/r/weihanli/dotnet-httpie) • +[📦 NuGet](https://www.nuget.org/packages/dotnet-httpie/) -- httpie: -- httpie docs: -- Curl to HTTPie request tool: +
diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 diff --git a/build/version.props b/build/version.props index 7d03ce9..f51b368 100644 --- a/build/version.props +++ b/build/version.props @@ -1,7 +1,7 @@ 0 - 14 + 15 0 $(VersionMajor).$(VersionMinor).$(VersionPatch) $(PackageVersion) diff --git a/docs/articles/README.md b/docs/articles/README.md new file mode 100644 index 0000000..ca94486 --- /dev/null +++ b/docs/articles/README.md @@ -0,0 +1,62 @@ +# dotnet-httpie Documentation + +Welcome to the comprehensive documentation for dotnet-httpie, a modern command-line HTTP client for .NET developers. + +## Table of Contents + +### Getting Started +- [Installation](installation.md) +- [Quick Start Guide](quick-start.md) +- [Basic Usage](basic-usage.md) + +### Core Features +- [Making HTTP Requests](http-requests.md) +- [Request Data Types](request-data-types.md) +- [Authentication](authentication.md) +- [File Operations](file-operations.md) + +### Advanced Features +- [Executing .http/.rest Files](file-execution.md) +- [Curl Command Execution](curl-execution.md) +- [Variable Substitution](variables.md) +- [Request Referencing](request-referencing.md) +- [JSON Schema Validation](json-schema-validation.md) +- [Request Caching](request-caching.md) +- [Load Testing](load-testing.md) + +### Configuration +- [Proxy Configuration](proxy-configuration.md) +- [SSL/TLS Configuration](ssl-configuration.md) +- [Environment Variables](environment-variables.md) + +### Docker & Deployment +- [Docker Usage](docker-usage.md) +- [CI/CD Integration](ci-cd-integration.md) + +### Advanced Topics +- [Middleware System](middleware-system.md) +- [Debugging & Troubleshooting](debugging.md) +- [Performance Tips](performance-tips.md) + +### Examples & Recipes +- [Common Use Cases](examples/common-use-cases.md) +- [API Testing Scenarios](examples/api-testing.md) +- [Integration Examples](examples/integrations.md) + +### Reference +- [Command Line Options](reference/command-line-options.md) +- [Configuration Reference](reference/configuration.md) +- [Error Codes](reference/error-codes.md) + +## Quick Links + +- [GitHub Repository](https://github.com/WeihanLi/dotnet-httpie) +- [NuGet Package](https://www.nuget.org/packages/dotnet-httpie/) +- [Release Notes](../ReleaseNotes.md) +- [Contributing Guidelines](../../CONTRIBUTING.md) + +## Need Help? + +- Check the [troubleshooting guide](debugging.md) +- Review [common examples](examples/common-use-cases.md) +- Visit our [GitHub Issues](https://github.com/WeihanLi/dotnet-httpie/issues) page \ No newline at end of file diff --git a/docs/articles/authentication.md b/docs/articles/authentication.md new file mode 100644 index 0000000..92aaaa9 --- /dev/null +++ b/docs/articles/authentication.md @@ -0,0 +1,506 @@ +# Authentication + +This guide covers various authentication methods supported by dotnet-httpie for securing your API requests. + +## Overview + +dotnet-httpie supports all common authentication methods used in modern APIs: + +- Bearer Token (JWT) +- API Key Authentication +- Basic Authentication +- Custom Header Authentication +- OAuth 2.0 flows +- Cookie-based Authentication + +## Bearer Token Authentication + +Most commonly used for JWT tokens in modern APIs. + +### Header-based Bearer Token + +```bash +# Standard Bearer token +dotnet-http GET api.example.com/protected \ + Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + +# Environment variable token +dotnet-http GET api.example.com/protected \ + Authorization:"Bearer $JWT_TOKEN" +``` + +### Getting JWT Tokens + +```bash +# Login to get JWT token +LOGIN_RESPONSE=$(dotnet-http POST api.example.com/auth/login \ + username="admin" \ + password="password" \ + --body) + +# Extract token using jq +TOKEN=$(echo $LOGIN_RESPONSE | jq -r '.access_token') + +# Use token for protected requests +dotnet-http GET api.example.com/users \ + Authorization:"Bearer $TOKEN" +``` + +### Refresh Token Flow + +```bash +# Use refresh token to get new access token +REFRESH_RESPONSE=$(dotnet-http POST api.example.com/auth/refresh \ + refresh_token="$REFRESH_TOKEN" \ + --body) + +NEW_TOKEN=$(echo $REFRESH_RESPONSE | jq -r '.access_token') + +# Use new token +dotnet-http GET api.example.com/protected \ + Authorization:"Bearer $NEW_TOKEN" +``` + +## API Key Authentication + +Common in REST APIs for service-to-service communication. + +### Header-based API Keys + +```bash +# Standard API key header +dotnet-http GET api.example.com/data \ + X-API-Key:"your-api-key-here" + +# Custom header names +dotnet-http GET api.example.com/data \ + X-RapidAPI-Key:"your-rapidapi-key" \ + X-RapidAPI-Host:"api.example.com" + +# Multiple API keys +dotnet-http GET api.example.com/data \ + X-API-Key:"primary-key" \ + X-Secondary-Key:"secondary-key" +``` + +### Query Parameter API Keys + +```bash +# API key as query parameter +dotnet-http GET api.example.com/data \ + api_key==your-api-key + +# Multiple parameters +dotnet-http GET api.example.com/data \ + key==your-api-key \ + format==json \ + version==v2 +``` + +### Environment-based API Keys + +```bash +# Store API key in environment variable +export API_KEY="your-secret-api-key" + +# Use in requests +dotnet-http GET api.example.com/data \ + X-API-Key:"$API_KEY" +``` + +## Basic Authentication + +Traditional username/password authentication. + +### Manual Basic Auth + +```bash +# Encode credentials manually +CREDENTIALS=$(echo -n 'username:password' | base64) +dotnet-http GET api.example.com/secure \ + Authorization:"Basic $CREDENTIALS" + +# Direct encoding +dotnet-http GET api.example.com/secure \ + Authorization:"Basic $(echo -n 'admin:secret123' | base64)" +``` + +### HTTPie-style Basic Auth + +```bash +# Using --auth flag (username:password format) +dotnet-http GET api.example.com/secure \ + --auth username:password + +# With explicit auth type +dotnet-http GET api.example.com/secure \ + --auth-type Basic --auth username:password +``` + +## OAuth 2.0 Flows + +### Client Credentials Flow + +```bash +# Get access token +TOKEN_RESPONSE=$(dotnet-http POST oauth.provider.com/token \ + grant_type="client_credentials" \ + client_id="your-client-id" \ + client_secret="your-client-secret" \ + scope="read write" \ + --body) + +ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token') + +# Use access token +dotnet-http GET api.example.com/protected \ + Authorization:"Bearer $ACCESS_TOKEN" +``` + +### Authorization Code Flow (Manual) + +```bash +# Step 1: Get authorization code (manual browser step) +echo "Visit: https://oauth.provider.com/authorize?client_id=your-client-id&response_type=code&redirect_uri=http://localhost:8080/callback&scope=read" + +# Step 2: Exchange code for token +TOKEN_RESPONSE=$(dotnet-http POST oauth.provider.com/token \ + grant_type="authorization_code" \ + client_id="your-client-id" \ + client_secret="your-client-secret" \ + code="authorization-code-from-callback" \ + redirect_uri="http://localhost:8080/callback" \ + --body) + +ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token') +``` + +### Resource Owner Password Credentials + +```bash +# Direct username/password exchange (less secure) +TOKEN_RESPONSE=$(dotnet-http POST oauth.provider.com/token \ + grant_type="password" \ + username="user@example.com" \ + password="user-password" \ + client_id="your-client-id" \ + client_secret="your-client-secret" \ + --body) + +ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token') +``` + +## Custom Authentication Schemes + +### Signature-based Authentication + +```bash +# AWS-style signature +SIGNATURE=$(echo -n "GET\n/api/data\n$(date -u)" | openssl dgst -sha256 -hmac "$SECRET_KEY" -binary | base64) + +dotnet-http GET api.example.com/data \ + Authorization:"AWS4-HMAC-SHA256 Credential=$ACCESS_KEY/$(date -u +%Y%m%d)/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=$SIGNATURE" \ + X-Amz-Date:"$(date -u +%Y%m%dT%H%M%SZ)" +``` + +### HMAC Authentication + +```bash +# Generate HMAC signature +TIMESTAMP=$(date +%s) +PAYLOAD="GET/api/data$TIMESTAMP" +SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET_KEY" -binary | base64) + +dotnet-http GET api.example.com/data \ + X-API-Key:"$API_KEY" \ + X-Timestamp:"$TIMESTAMP" \ + X-Signature:"$SIGNATURE" +``` + +### Digest Authentication + +```bash +# Simple digest implementation (for demonstration) +NONCE=$(openssl rand -hex 16) +HASH=$(echo -n "username:realm:password" | md5sum | cut -d' ' -f1) +RESPONSE=$(echo -n "$HASH:$NONCE:GET:/api/data" | md5sum | cut -d' ' -f1) + +dotnet-http GET api.example.com/data \ + Authorization:"Digest username=\"username\", realm=\"realm\", nonce=\"$NONCE\", uri=\"/api/data\", response=\"$RESPONSE\"" +``` + +## Cookie-based Authentication + +### Session Cookies + +```bash +# Login and save cookies +dotnet-http POST api.example.com/login \ + username="admin" \ + password="password" \ + --session=api-session + +# Use saved session for subsequent requests +dotnet-http GET api.example.com/protected \ + --session=api-session +``` + +### Manual Cookie Handling + +```bash +# Set cookies manually +dotnet-http GET api.example.com/protected \ + Cookie:"sessionid=abc123; csrftoken=xyz789" + +# Multiple cookies +dotnet-http GET api.example.com/protected \ + Cookie:"auth_token=token123; user_pref=dark_mode; lang=en" +``` + +## Multi-factor Authentication + +### TOTP (Time-based One-time Password) + +```bash +# Generate TOTP code (using external tool) +TOTP_CODE=$(oathtool --totp --base32 "$TOTP_SECRET") + +# Include in request +dotnet-http POST api.example.com/sensitive-action \ + Authorization:"Bearer $TOKEN" \ + X-TOTP-Code:"$TOTP_CODE" \ + action="transfer" \ + amount:=1000 +``` + +### SMS/Email Verification + +```bash +# Request verification code +dotnet-http POST api.example.com/request-verification \ + Authorization:"Bearer $TOKEN" \ + phone="+1-555-0123" + +# Submit verification code +dotnet-http POST api.example.com/verify \ + Authorization:"Bearer $TOKEN" \ + verification_code="123456" \ + action="sensitive-operation" +``` + +## Authentication in HTTP Files + +### Environment Variables + +```http +# auth-example.http +@baseUrl = https://api.example.com +@token = {{$env JWT_TOKEN}} +@apiKey = {{$env API_KEY}} + +### + +# Bearer token from environment +GET {{baseUrl}}/protected +Authorization: Bearer {{token}} + +### + +# API key from environment +GET {{baseUrl}}/data +X-API-Key: {{apiKey}} +``` + +### Login Flow in HTTP Files + +```http +# complete-auth-flow.http +@baseUrl = https://api.example.com + +### + +# @name login +POST {{baseUrl}}/auth/login +Content-Type: application/json + +{ + "username": "admin", + "password": "password" +} + +### + +# @name getProfile +GET {{baseUrl}}/profile +Authorization: Bearer {{login.response.body.access_token}} + +### + +# @name updateProfile +PUT {{baseUrl}}/profile +Authorization: Bearer {{login.response.body.access_token}} +Content-Type: application/json + +{ + "name": "Updated Name", + "email": "new@example.com" +} +``` + +## Security Best Practices + +### Environment Variables + +```bash +# Never hardcode secrets in commands or files +# Use environment variables instead + +# Bad +dotnet-http GET api.example.com/data X-API-Key:"secret-key-123" + +# Good +export API_KEY="secret-key-123" +dotnet-http GET api.example.com/data X-API-Key:"$API_KEY" +``` + +### Secure Storage + +```bash +# Use secure credential storage +# Example with macOS Keychain +security add-internet-password -s "api.example.com" -a "myapp" -w "secret-api-key" +API_KEY=$(security find-internet-password -s "api.example.com" -a "myapp" -w) + +dotnet-http GET api.example.com/data X-API-Key:"$API_KEY" +``` + +### Token Rotation + +```bash +#!/bin/bash +# token-rotation.sh + +# Check if token is expired +if ! dotnet-http GET api.example.com/verify Authorization:"Bearer $ACCESS_TOKEN" >/dev/null 2>&1; then + echo "Token expired, refreshing..." + + # Refresh token + NEW_TOKEN_RESPONSE=$(dotnet-http POST api.example.com/auth/refresh \ + refresh_token="$REFRESH_TOKEN" --body) + + ACCESS_TOKEN=$(echo $NEW_TOKEN_RESPONSE | jq -r '.access_token') + export ACCESS_TOKEN + + echo "Token refreshed successfully" +fi + +# Use current token +dotnet-http GET api.example.com/protected Authorization:"Bearer $ACCESS_TOKEN" +``` + +## Platform-Specific Examples + +### GitHub API + +```bash +# Personal access token +dotnet-http GET api.github.com/user \ + Authorization:"token $GITHUB_TOKEN" + +# Create repository +dotnet-http POST api.github.com/user/repos \ + Authorization:"token $GITHUB_TOKEN" \ + name="new-repo" \ + description="Created via dotnet-httpie" +``` + +### AWS API + +```bash +# AWS Signature Version 4 (simplified) +AWS_ACCESS_KEY="your-access-key" +AWS_SECRET_KEY="your-secret-key" +AWS_REGION="us-east-1" +SERVICE="s3" + +# Note: Full AWS sig v4 implementation would be more complex +dotnet-http GET s3.amazonaws.com/bucket-name \ + Authorization:"AWS4-HMAC-SHA256 ..." \ + X-Amz-Date:"$(date -u +%Y%m%dT%H%M%SZ)" +``` + +### Google APIs + +```bash +# OAuth 2.0 with Google +# First, get OAuth token through browser flow +# Then use the access token + +dotnet-http GET www.googleapis.com/oauth2/v1/userinfo \ + Authorization:"Bearer $GOOGLE_ACCESS_TOKEN" + +# Service account authentication (with JWT) +dotnet-http GET www.googleapis.com/storage/v1/b \ + Authorization:"Bearer $SERVICE_ACCOUNT_JWT" +``` + +## Testing Authentication + +### Verify Token Validity + +```bash +# Test if token is valid +if dotnet-http GET api.example.com/verify Authorization:"Bearer $TOKEN" --check-status; then + echo "Token is valid" +else + echo "Token is invalid or expired" +fi +``` + +### Authentication Debugging + +```bash +# Debug authentication issues +dotnet-http GET api.example.com/protected \ + Authorization:"Bearer $TOKEN" \ + --debug \ + --offline # Preview the request first +``` + +### Automated Authentication Testing + +```bash +#!/bin/bash +# auth-test.sh + +# Test various authentication methods +echo "Testing authentication methods..." + +# Test API key +if dotnet-http GET api.example.com/test X-API-Key:"$API_KEY" --check-status; then + echo "✓ API Key authentication works" +else + echo "✗ API Key authentication failed" +fi + +# Test JWT token +if dotnet-http GET api.example.com/test Authorization:"Bearer $JWT_TOKEN" --check-status; then + echo "✓ JWT authentication works" +else + echo "✗ JWT authentication failed" +fi + +# Test basic auth +if dotnet-http GET api.example.com/test Authorization:"Basic $BASIC_AUTH" --check-status; then + echo "✓ Basic authentication works" +else + echo "✗ Basic authentication failed" +fi +``` + +## Next Steps + +- Learn about [request data types](request-data-types.md) for sending authenticated requests +- Explore [file execution](file-execution.md) for managing authentication in HTTP files +- Check out [environment variables](environment-variables.md) for secure credential management +- Review [examples](examples/common-use-cases.md) for real-world authentication patterns \ No newline at end of file diff --git a/docs/articles/basic-usage.md b/docs/articles/basic-usage.md new file mode 100644 index 0000000..9fd28b4 --- /dev/null +++ b/docs/articles/basic-usage.md @@ -0,0 +1,500 @@ +# Basic Usage + +This guide covers the fundamental concepts and basic usage patterns of dotnet-httpie. + +## Command Structure + +The basic syntax for dotnet-httpie commands: + +``` +dotnet-http [flags] [METHOD] URL [ITEM [ITEM]] +``` + +### Components + +- **flags**: Optional command flags (e.g., `--offline`, `--debug`, `--body`) +- **METHOD**: HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) +- **URL**: Target URL (can be full URL or shortened format) +- **ITEM**: Request items (query parameters, headers, data) + +## HTTP Methods + +### GET (Default) + +```bash +# Simple GET request +dotnet-http httpbin.org/get + +# GET with query parameters +dotnet-http httpbin.org/get name==John age==30 + +# Explicit GET method +dotnet-http GET httpbin.org/get search==query +``` + +### POST + +```bash +# POST with JSON data +dotnet-http POST httpbin.org/post name=John email=john@example.com + +# POST with form data +dotnet-http POST httpbin.org/post --form name=John email=john@example.com +``` + +### PUT + +```bash +# PUT request (typically for updates) +dotnet-http PUT httpbin.org/put id:=123 name=John +``` + +### DELETE + +```bash +# DELETE request +dotnet-http DELETE httpbin.org/delete + +# DELETE with parameters +dotnet-http DELETE api.example.com/users/123 +``` + +### Other Methods + +```bash +# PATCH for partial updates +dotnet-http PATCH httpbin.org/patch status=active + +# HEAD for headers only +dotnet-http HEAD httpbin.org/get + +# OPTIONS for allowed methods +dotnet-http OPTIONS httpbin.org +``` + +## URL Formats + +### Full URLs + +```bash +# HTTPS URLs +dotnet-http https://api.example.com/users + +# HTTP URLs +dotnet-http http://localhost:3000/api/data + +# URLs with ports +dotnet-http https://api.example.com:8443/secure +``` + +### Shortened URLs + +```bash +# Localhost shortcuts +dotnet-http :3000/api/users # → http://localhost:3000/api/users +dotnet-http localhost:5000/health # → http://localhost:5000/health + +# HTTPS by default for domains +dotnet-http api.example.com/data # → https://api.example.com/data +``` + +### URL with Paths + +```bash +# Simple paths +dotnet-http api.example.com/v1/users + +# Complex paths with parameters +dotnet-http api.example.com/users/123/posts/456 + +# Paths with special characters +dotnet-http "api.example.com/search?q=hello world" +``` + +## Request Items + +### Query Parameters (`==`) + +Query parameters are added to the URL: + +```bash +# Single parameter +dotnet-http httpbin.org/get name==John + +# Multiple parameters +dotnet-http httpbin.org/get name==John age==30 city=="New York" + +# Arrays/multiple values +dotnet-http httpbin.org/get tag==javascript tag==web tag==api +``` + +### Headers (`:`) + +Headers control request behavior: + +```bash +# Authentication header +dotnet-http httpbin.org/headers Authorization:"Bearer token123" + +# Content type +dotnet-http POST httpbin.org/post Content-Type:"application/xml" + +# Multiple headers +dotnet-http httpbin.org/headers \ + Authorization:"Bearer token" \ + User-Agent:"MyApp/1.0" \ + Accept:"application/json" +``` + +### JSON Data (`=`) + +Creates JSON request body: + +```bash +# Simple fields +dotnet-http POST httpbin.org/post name=John age=30 + +# Creates: {"name": "John", "age": "30"} +``` + +### Raw JSON Data (`:=`) + +For typed JSON values: + +```bash +# Numbers +dotnet-http POST httpbin.org/post age:=30 price:=19.99 + +# Booleans +dotnet-http POST httpbin.org/post active:=true published:=false + +# Arrays +dotnet-http POST httpbin.org/post tags:='["web", "api", "tool"]' + +# Objects +dotnet-http POST httpbin.org/post profile:='{"name": "John", "level": 5}' + +# Creates: {"age": 30, "price": 19.99, "active": true, "published": false, "tags": ["web", "api", "tool"], "profile": {"name": "John", "level": 5}} +``` + +## Common Patterns + +### API Testing + +```bash +# Health check +dotnet-http GET api.example.com/health + +# Get list of resources +dotnet-http GET api.example.com/users + +# Get specific resource +dotnet-http GET api.example.com/users/123 + +# Create new resource +dotnet-http POST api.example.com/users name=John email=john@example.com + +# Update resource +dotnet-http PUT api.example.com/users/123 name="John Smith" + +# Delete resource +dotnet-http DELETE api.example.com/users/123 +``` + +### Authentication Patterns + +```bash +# Bearer token +dotnet-http GET api.example.com/protected \ + Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + +# API key in header +dotnet-http GET api.example.com/data \ + X-API-Key:"your-api-key" + +# API key in query +dotnet-http GET api.example.com/data \ + api_key==your-api-key + +# Basic authentication +dotnet-http GET api.example.com/secure \ + Authorization:"Basic $(echo -n 'user:pass' | base64)" +``` + +### Data Submission Patterns + +```bash +# Simple form data +dotnet-http POST api.example.com/contact \ + name=John \ + email=john@example.com \ + message="Hello from dotnet-httpie" + +# Complex nested data +dotnet-http POST api.example.com/orders \ + customer[name]=John \ + customer[email]=john@example.com \ + items[0][id]:=1 \ + items[0][quantity]:=2 \ + items[1][id]:=2 \ + items[1][quantity]:=1 \ + total:=99.99 + +# File upload +dotnet-http POST api.example.com/upload \ + --multipart \ + description="My document" \ + file@/path/to/document.pdf +``` + +## Response Handling + +### Default Response + +Shows headers and body: + +```bash +dotnet-http GET httpbin.org/get +``` + +### Body Only + +```bash +# Only response body +dotnet-http GET httpbin.org/get --body + +# Useful for piping to other tools +dotnet-http GET api.example.com/users --body | jq '.users[0]' +``` + +### Headers Only + +```bash +# Only response headers +dotnet-http HEAD httpbin.org/get +``` + +### Save Response + +```bash +# Save to file +dotnet-http GET api.example.com/report --body > report.json + +# Download files +dotnet-http GET api.example.com/files/document.pdf --download +``` + +## Useful Flags + +### Debug Mode + +Get detailed information about the request: + +```bash +dotnet-http GET api.example.com/data --debug +``` + +### Offline Mode + +Preview the request without sending it: + +```bash +dotnet-http POST api.example.com/users name=John --offline +``` + +### Check Status + +Exit with non-zero code for HTTP errors: + +```bash +if dotnet-http GET api.example.com/health --check-status; then + echo "API is healthy" +else + echo "API is down" +fi +``` + +## Working with JSON + +### Simple JSON + +```bash +# String values (default) +dotnet-http POST httpbin.org/post name=John title="Software Engineer" + +# Number values +dotnet-http POST httpbin.org/post age:=30 salary:=75000 + +# Boolean values +dotnet-http POST httpbin.org/post active:=true verified:=false + +# Null values +dotnet-http POST httpbin.org/post middle_name:=null +``` + +### Complex JSON + +```bash +# Arrays +dotnet-http POST httpbin.org/post \ + skills:='["C#", "JavaScript", "Python"]' \ + scores:='[95, 87, 92]' + +# Nested objects +dotnet-http POST httpbin.org/post \ + address:='{"street": "123 Main St", "city": "Seattle", "zip": "98101"}' \ + contact:='{"email": "john@example.com", "phone": "+1-555-0123"}' + +# Mixed complex data +dotnet-http POST httpbin.org/post \ + name=John \ + age:=30 \ + active:=true \ + skills:='["programming", "testing"]' \ + address:='{"city": "Seattle", "state": "WA"}' \ + metadata:=null +``` + +## Error Handling + +### HTTP Status Codes + +```bash +# dotnet-httpie shows HTTP errors clearly +dotnet-http GET httpbin.org/status/404 # Shows 404 Not Found +dotnet-http GET httpbin.org/status/500 # Shows 500 Internal Server Error +``` + +### Debugging Errors + +```bash +# Use debug mode to see detailed error information +dotnet-http GET api.example.com/broken --debug + +# Check request format first +dotnet-http POST api.example.com/users invalid-data --offline +``` + +## Tips and Best Practices + +### 1. Use Environment Variables + +```bash +# Store API tokens in environment variables +export API_TOKEN="your-secret-token" +dotnet-http GET api.example.com/protected Authorization:"Bearer $API_TOKEN" + +# Store base URLs +export API_BASE="https://api.example.com" +dotnet-http GET "$API_BASE/users" +``` + +### 2. Quote Special Characters + +```bash +# Quote values with spaces or special characters +dotnet-http POST httpbin.org/post message="Hello, world!" tags:='["tag with spaces", "special!chars"]' +``` + +### 3. Use Files for Large Data + +```bash +# Instead of long command lines, use files +cat > user.json << EOF +{ + "name": "John Doe", + "email": "john@example.com", + "address": { + "street": "123 Main St", + "city": "Seattle", + "state": "WA", + "zip": "98101" + } +} +EOF + +dotnet-http POST api.example.com/users @user.json +``` + +### 4. Combine with Other Tools + +```bash +# Extract specific data with jq +USER_ID=$(dotnet-http POST api.example.com/users name=John --body | jq -r '.id') +dotnet-http GET "api.example.com/users/$USER_ID" + +# Format JSON output +dotnet-http GET api.example.com/users | jq . + +# Save and process responses +dotnet-http GET api.example.com/users --body > users.json +jq '.users[] | select(.active == true)' users.json +``` + +### 5. Test Incrementally + +```bash +# Start with simple requests +dotnet-http GET api.example.com/health + +# Add authentication +dotnet-http GET api.example.com/protected Authorization:"Bearer $TOKEN" + +# Add data gradually +dotnet-http POST api.example.com/users name=John +dotnet-http POST api.example.com/users name=John email=john@example.com +dotnet-http POST api.example.com/users name=John email=john@example.com age:=30 +``` + +## Common Use Cases + +### Development Workflow + +```bash +# 1. Check if API is running +dotnet-http GET localhost:3000/health + +# 2. Test authentication +dotnet-http POST localhost:3000/auth/login username=admin password=password + +# 3. Test CRUD operations +dotnet-http GET localhost:3000/api/users +dotnet-http POST localhost:3000/api/users name=Test email=test@example.com +dotnet-http PUT localhost:3000/api/users/1 name="Updated Name" +dotnet-http DELETE localhost:3000/api/users/1 +``` + +### API Exploration + +```bash +# Discover API endpoints +dotnet-http OPTIONS api.example.com + +# Check API documentation endpoint +dotnet-http GET api.example.com/docs + +# Test different response formats +dotnet-http GET api.example.com/users Accept:"application/json" +dotnet-http GET api.example.com/users Accept:"application/xml" +``` + +### Integration Testing + +```bash +# Test service dependencies +dotnet-http GET auth-service.internal/health +dotnet-http GET user-service.internal/health +dotnet-http GET order-service.internal/health + +# Test cross-service communication +TOKEN=$(dotnet-http POST auth-service.internal/token client_id=test --body | jq -r '.access_token') +dotnet-http GET user-service.internal/profile Authorization:"Bearer $TOKEN" +``` + +## Next Steps + +- Learn about [advanced request data types](request-data-types.md) +- Explore [authentication methods](authentication.md) +- Try [file execution](file-execution.md) for complex workflows +- Check out [common use cases](examples/common-use-cases.md) for real-world examples +- Use [debugging techniques](debugging.md) when things go wrong \ No newline at end of file diff --git a/docs/articles/ci-cd-integration.md b/docs/articles/ci-cd-integration.md new file mode 100644 index 0000000..b69cda0 --- /dev/null +++ b/docs/articles/ci-cd-integration.md @@ -0,0 +1,824 @@ +# CI/CD Integration + +This guide shows how to integrate dotnet-httpie into various CI/CD pipelines for automated API testing, health checks, and deployment verification. + +## Overview + +dotnet-httpie is perfect for CI/CD scenarios because it: +- Provides deterministic exit codes +- Supports scriptable automation +- Works in containerized environments +- Handles authentication securely +- Offers offline mode for validation + +## GitHub Actions + +### Basic API Testing + +```yaml +name: API Tests +on: [push, pull_request] + +jobs: + api-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '8.0' + + - name: Install dotnet-httpie + run: dotnet tool install --global dotnet-httpie + + - name: API Health Check + run: dotnet-http GET ${{ vars.API_BASE_URL }}/health + + - name: Run API Test Suite + run: dotnet-http exec tests/api-integration.http --env testing + env: + API_TOKEN: ${{ secrets.API_TOKEN }} +``` + +### Multi-Environment Testing + +```yaml +name: Multi-Environment Tests +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + environment: [development, staging, production] + steps: + - uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '8.0' + + - name: Install dotnet-httpie + run: dotnet tool install --global dotnet-httpie + + - name: Test ${{ matrix.environment }} + run: dotnet-http exec tests/smoke-tests.http --env ${{ matrix.environment }} + env: + API_TOKEN: ${{ secrets[format('API_TOKEN_{0}', matrix.environment)] }} + API_BASE_URL: ${{ vars[format('API_BASE_URL_{0}', matrix.environment)] }} +``` + +### Docker-based Testing + +```yaml +name: Docker API Tests +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + services: + api: + image: my-api:latest + ports: + - 3000:3000 + env: + DATABASE_URL: postgresql://test:test@postgres:5432/testdb + postgres: + image: postgres:13 + env: + POSTGRES_DB: testdb + POSTGRES_USER: test + POSTGRES_PASSWORD: test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v3 + + - name: Wait for API to be ready + run: | + timeout 60 bash -c 'until docker run --rm --network host weihanli/dotnet-httpie:latest GET localhost:3000/health; do sleep 2; done' + + - name: Run Integration Tests + run: | + docker run --rm --network host \ + -v ${{ github.workspace }}:/workspace -w /workspace \ + weihanli/dotnet-httpie:latest exec tests/integration.http --env ci +``` + +### Deployment Verification + +```yaml +name: Deploy and Verify +on: + push: + branches: [main] + +jobs: + deploy-and-verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + # Deploy steps here... + + - name: Install dotnet-httpie + run: dotnet tool install --global dotnet-httpie + + - name: Verify Deployment + run: | + # Wait for deployment to be ready + sleep 30 + + # Health check + dotnet-http GET ${{ vars.PRODUCTION_API_URL }}/health + + # Smoke tests + dotnet-http exec tests/post-deployment.http --env production + env: + PRODUCTION_API_TOKEN: ${{ secrets.PRODUCTION_API_TOKEN }} + + - name: Rollback on Failure + if: failure() + run: | + echo "Deployment verification failed, initiating rollback..." + # Rollback logic here +``` + +## Azure DevOps + +### Basic Pipeline + +```yaml +trigger: +- main + +pool: + vmImage: 'ubuntu-latest' + +variables: + apiBaseUrl: 'https://api.example.com' + +steps: +- task: UseDotNet@2 + displayName: 'Setup .NET SDK' + inputs: + packageType: 'sdk' + version: '8.0.x' + +- script: dotnet tool install --global dotnet-httpie + displayName: 'Install dotnet-httpie' + +- script: dotnet-http GET $(apiBaseUrl)/health + displayName: 'API Health Check' + +- script: dotnet-http exec tests/api-tests.http --env $(Environment) + displayName: 'Run API Tests' + env: + API_TOKEN: $(ApiToken) +``` + +### Multi-Stage Pipeline + +```yaml +trigger: +- main + +stages: +- stage: Test + displayName: 'Test Stage' + jobs: + - job: ApiTests + displayName: 'API Tests' + pool: + vmImage: 'ubuntu-latest' + steps: + - task: UseDotNet@2 + inputs: + packageType: 'sdk' + version: '8.0.x' + + - script: dotnet tool install --global dotnet-httpie + displayName: 'Install dotnet-httpie' + + - script: dotnet-http exec tests/unit-api-tests.http --env testing + displayName: 'Unit API Tests' + env: + API_TOKEN: $(TestApiToken) + +- stage: Deploy + displayName: 'Deploy Stage' + dependsOn: Test + condition: succeeded() + jobs: + - deployment: DeployAPI + displayName: 'Deploy API' + environment: 'production' + strategy: + runOnce: + deploy: + steps: + # Deployment steps... + + - script: dotnet tool install --global dotnet-httpie + displayName: 'Install dotnet-httpie' + + - script: | + # Wait for deployment + sleep 60 + + # Verify deployment + dotnet-http GET $(ProductionApiUrl)/health + dotnet-http exec tests/production-smoke.http --env production + displayName: 'Verify Deployment' + env: + PRODUCTION_API_TOKEN: $(ProductionApiToken) +``` + +## GitLab CI + +### Basic Configuration + +```yaml +stages: + - test + - deploy + - verify + +variables: + DOTNET_VERSION: "8.0" + +before_script: + - apt-get update -qy + - apt-get install -y dotnet-sdk-8.0 + - dotnet tool install --global dotnet-httpie + - export PATH="$PATH:/root/.dotnet/tools" + +api-tests: + stage: test + script: + - dotnet-http GET $API_BASE_URL/health + - dotnet-http exec tests/api-suite.http --env $CI_ENVIRONMENT_NAME + variables: + API_BASE_URL: "https://api-test.example.com" + environment: + name: testing + +deploy-production: + stage: deploy + script: + - echo "Deploying to production..." + # Deployment logic here + only: + - main + +verify-production: + stage: verify + script: + - sleep 30 # Wait for deployment + - dotnet-http GET $PRODUCTION_API_URL/health + - dotnet-http exec tests/production-verification.http --env production + variables: + PRODUCTION_API_URL: "https://api.example.com" + environment: + name: production + dependencies: + - deploy-production + only: + - main +``` + +### Docker-based GitLab CI + +```yaml +image: mcr.microsoft.com/dotnet/sdk:8.0 + +stages: + - test + - deploy + +api-tests: + stage: test + services: + - name: postgres:13 + alias: postgres + - name: redis:6 + alias: redis + before_script: + - dotnet tool install --global dotnet-httpie + - export PATH="$PATH:/root/.dotnet/tools" + script: + - dotnet-http GET http://api-container:3000/health + - dotnet-http exec tests/integration.http --env gitlab-ci + variables: + POSTGRES_DB: testdb + POSTGRES_USER: test + POSTGRES_PASSWORD: test +``` + +## Jenkins + +### Declarative Pipeline + +```groovy +pipeline { + agent any + + environment { + DOTNET_VERSION = '8.0' + API_BASE_URL = 'https://api.example.com' + } + + stages { + stage('Setup') { + steps { + sh ''' + # Install .NET SDK if not available + if ! command -v dotnet &> /dev/null; then + wget https://dot.net/v1/dotnet-install.sh + chmod +x dotnet-install.sh + ./dotnet-install.sh --version ${DOTNET_VERSION} + export PATH="$PATH:$HOME/.dotnet" + fi + + # Install dotnet-httpie + dotnet tool install --global dotnet-httpie + ''' + } + } + + stage('API Health Check') { + steps { + sh 'dotnet-http GET ${API_BASE_URL}/health' + } + } + + stage('API Tests') { + steps { + withCredentials([string(credentialsId: 'api-token', variable: 'API_TOKEN')]) { + sh ''' + export API_TOKEN=${API_TOKEN} + dotnet-http exec tests/api-tests.http --env ${BRANCH_NAME} + ''' + } + } + } + + stage('Deploy') { + when { + branch 'main' + } + steps { + sh 'echo "Deploying to production..."' + // Deployment steps + } + } + + stage('Verify Deployment') { + when { + branch 'main' + } + steps { + sh ''' + sleep 30 + dotnet-http GET ${API_BASE_URL}/health + dotnet-http exec tests/production-smoke.http --env production + ''' + } + } + } + + post { + failure { + emailext ( + subject: "Pipeline Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}", + body: "API tests failed. Check the build logs for details.", + to: "${env.CHANGE_AUTHOR_EMAIL}" + ) + } + } +} +``` + +## CircleCI + +```yaml +version: 2.1 + +orbs: + dotnet: circleci/dotnet@2.0.0 + +jobs: + api-tests: + docker: + - image: mcr.microsoft.com/dotnet/sdk:8.0 + steps: + - checkout + - run: + name: Install dotnet-httpie + command: dotnet tool install --global dotnet-httpie + - run: + name: Add tools to PATH + command: echo 'export PATH="$PATH:/root/.dotnet/tools"' >> $BASH_ENV + - run: + name: API Health Check + command: dotnet-http GET $API_BASE_URL/health + - run: + name: Run API Tests + command: dotnet-http exec tests/api-tests.http --env testing + + deploy: + docker: + - image: cimg/base:stable + steps: + - checkout + - run: + name: Deploy to production + command: echo "Deploying..." + + verify-deployment: + docker: + - image: mcr.microsoft.com/dotnet/sdk:8.0 + steps: + - checkout + - run: + name: Install dotnet-httpie + command: dotnet tool install --global dotnet-httpie + - run: + name: Add tools to PATH + command: echo 'export PATH="$PATH:/root/.dotnet/tools"' >> $BASH_ENV + - run: + name: Verify Production Deployment + command: | + sleep 30 + dotnet-http GET $PRODUCTION_API_URL/health + dotnet-http exec tests/production-verification.http --env production + +workflows: + test-deploy-verify: + jobs: + - api-tests + - deploy: + requires: + - api-tests + filters: + branches: + only: main + - verify-deployment: + requires: + - deploy +``` + +## Docker Compose for Testing + +### Local Integration Testing + +```yaml +# docker-compose.test.yml +version: '3.8' + +services: + api: + build: . + ports: + - "3000:3000" + environment: + - NODE_ENV=test + - DATABASE_URL=postgresql://test:test@postgres:5432/testdb + depends_on: + postgres: + condition: service_healthy + + postgres: + image: postgres:13 + environment: + POSTGRES_DB: testdb + POSTGRES_USER: test + POSTGRES_PASSWORD: test + healthcheck: + test: ["CMD-SHELL", "pg_isready -U test"] + interval: 5s + timeout: 5s + retries: 5 + + api-tests: + image: weihanli/dotnet-httpie:latest + depends_on: + - api + volumes: + - ./tests:/tests + command: > + sh -c " + sleep 10 && + dotnet-http GET http://api:3000/health && + dotnet-http exec /tests/integration-tests.http --env docker + " + environment: + - API_BASE_URL=http://api:3000 +``` + +## Kubernetes Jobs + +### API Testing Job + +```yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: api-tests +spec: + template: + spec: + containers: + - name: api-tests + image: weihanli/dotnet-httpie:latest + command: ["/bin/sh"] + args: + - -c + - | + dotnet-http GET $API_BASE_URL/health + dotnet-http exec /tests/k8s-tests.http --env kubernetes + env: + - name: API_BASE_URL + value: "http://api-service:8080" + - name: API_TOKEN + valueFrom: + secretKeyRef: + name: api-secrets + key: token + volumeMounts: + - name: test-files + mountPath: /tests + volumes: + - name: test-files + configMap: + name: api-test-files + restartPolicy: Never + backoffLimit: 3 +``` + +### CronJob for Health Monitoring + +```yaml +apiVersion: batch/v1 +kind: CronJob +metadata: + name: api-health-monitor +spec: + schedule: "*/5 * * * *" # Every 5 minutes + jobTemplate: + spec: + template: + spec: + containers: + - name: health-check + image: weihanli/dotnet-httpie:latest + command: ["/bin/sh"] + args: + - -c + - | + if ! dotnet-http GET $API_BASE_URL/health --check-status; then + echo "Health check failed" + exit 1 + fi + env: + - name: API_BASE_URL + value: "http://api-service:8080" + restartPolicy: OnFailure +``` + +## Test Patterns + +### Health Check Scripts + +```bash +#!/bin/bash +# health-check.sh + +set -e + +API_BASE_URL="${API_BASE_URL:-http://localhost:3000}" +MAX_RETRIES="${MAX_RETRIES:-30}" +RETRY_DELAY="${RETRY_DELAY:-2}" + +echo "Waiting for API to be ready at $API_BASE_URL..." + +for i in $(seq 1 $MAX_RETRIES); do + if dotnet-http GET "$API_BASE_URL/health" --check-status >/dev/null 2>&1; then + echo "API is ready after $i attempts" + exit 0 + fi + + echo "Attempt $i/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..." + sleep $RETRY_DELAY +done + +echo "API failed to become ready after $MAX_RETRIES attempts" +exit 1 +``` + +### Smoke Test Suite + +```bash +#!/bin/bash +# smoke-tests.sh + +set -e + +API_BASE_URL="${API_BASE_URL:-http://localhost:3000}" +ENVIRONMENT="${ENVIRONMENT:-development}" + +echo "Running smoke tests for $ENVIRONMENT environment..." + +# Critical endpoints +ENDPOINTS=( + "/health" + "/api/v1/status" + "/api/v1/version" +) + +for endpoint in "${ENDPOINTS[@]}"; do + echo "Testing $endpoint..." + if dotnet-http GET "$API_BASE_URL$endpoint" --check-status; then + echo "✓ $endpoint OK" + else + echo "✗ $endpoint FAILED" + exit 1 + fi +done + +echo "All smoke tests passed!" +``` + +### Load Testing + +```bash +#!/bin/bash +# load-test.sh + +ENDPOINT="${1:-http://localhost:3000/api/test}" +CONCURRENT="${2:-10}" +REQUESTS="${3:-100}" + +echo "Load testing $ENDPOINT with $CONCURRENT concurrent users, $REQUESTS total requests" + +# Create temporary results file +RESULTS_FILE=$(mktemp) + +# Function to run requests +run_requests() { + local requests_per_worker=$1 + for i in $(seq 1 $requests_per_worker); do + start_time=$(date +%s%N) + if dotnet-http GET "$ENDPOINT" >/dev/null 2>&1; then + end_time=$(date +%s%N) + duration=$(((end_time - start_time) / 1000000)) + echo "SUCCESS,$duration" >> "$RESULTS_FILE" + else + echo "FAILURE,0" >> "$RESULTS_FILE" + fi + done +} + +# Start concurrent workers +REQUESTS_PER_WORKER=$((REQUESTS / CONCURRENT)) +for i in $(seq 1 $CONCURRENT); do + run_requests $REQUESTS_PER_WORKER & +done + +# Wait for all workers to complete +wait + +# Analyze results +total=$(wc -l < "$RESULTS_FILE") +successful=$(grep "SUCCESS" "$RESULTS_FILE" | wc -l) +failed=$((total - successful)) + +if [ $successful -gt 0 ]; then + avg_response_time=$(grep "SUCCESS" "$RESULTS_FILE" | cut -d, -f2 | awk '{sum+=$1} END {print sum/NR}') +else + avg_response_time=0 +fi + +echo "Load test results:" +echo " Total requests: $total" +echo " Successful: $successful" +echo " Failed: $failed" +echo " Success rate: $(( successful * 100 / total ))%" +echo " Average response time: ${avg_response_time}ms" + +# Cleanup +rm "$RESULTS_FILE" + +# Exit with error if failure rate is too high +if [ $((failed * 100 / total)) -gt 5 ]; then + echo "Failure rate too high!" + exit 1 +fi +``` + +## Best Practices + +### 1. Environment Management + +```bash +# Use environment-specific configurations +dotnet-http exec tests/api-tests.http --env $CI_ENVIRONMENT_NAME + +# Store secrets securely in CI/CD variables +export API_TOKEN="$CI_API_TOKEN" +dotnet-http GET api.example.com/protected Authorization:"Bearer $API_TOKEN" +``` + +### 2. Error Handling + +```bash +# Proper error handling in scripts +if ! dotnet-http GET api.example.com/health --check-status; then + echo "Health check failed, aborting deployment" + exit 1 +fi + +# Retry logic for flaky endpoints +for i in {1..3}; do + if dotnet-http GET api.example.com/flaky-endpoint; then + break + elif [ $i -eq 3 ]; then + echo "Endpoint failed after 3 attempts" + exit 1 + else + echo "Attempt $i failed, retrying..." + sleep 5 + fi +done +``` + +### 3. Parallel Testing + +```bash +# Run tests in parallel for faster execution +{ + dotnet-http exec tests/user-api.http --env $ENV & + dotnet-http exec tests/order-api.http --env $ENV & + dotnet-http exec tests/payment-api.http --env $ENV & + wait +} && echo "All API tests completed successfully" +``` + +### 4. Reporting + +```bash +# Generate test reports +{ + echo "# API Test Report" + echo "Generated: $(date)" + echo "" + + if dotnet-http GET api.example.com/health; then + echo "✅ Health Check: PASSED" + else + echo "❌ Health Check: FAILED" + fi + + # More test results... +} > test-report.md +``` + +## Troubleshooting CI/CD Issues + +### Common Problems + +1. **Tool not found**: Ensure dotnet-httpie is installed and in PATH +2. **Network issues**: Check firewall rules and DNS resolution +3. **Authentication failures**: Verify secrets and environment variables +4. **Timeout issues**: Increase timeouts for slow networks +5. **SSL certificate problems**: Use `--verify=no` for development (not production) + +### Debug CI/CD Issues + +```bash +# Enable debug mode in CI +dotnet-http GET api.example.com/data --debug + +# Check environment variables +env | grep -i api + +# Test network connectivity +dotnet-http GET httpbin.org/get # External connectivity +dotnet-http GET localhost:3000/health # Local connectivity +``` + +## Next Steps + +- Set up [monitoring and alerting](../examples/common-use-cases.md) with dotnet-httpie +- Explore [Docker usage](../docker-usage.md) for containerized CI/CD +- Learn about [debugging techniques](../debugging.md) for troubleshooting +- Review [authentication methods](../authentication.md) for secure CI/CD \ No newline at end of file diff --git a/docs/articles/debugging.md b/docs/articles/debugging.md new file mode 100644 index 0000000..5e68b1e --- /dev/null +++ b/docs/articles/debugging.md @@ -0,0 +1,593 @@ +# Debugging & Troubleshooting + +This guide helps you debug issues with dotnet-httpie and troubleshoot common problems. + +## Debug Mode + +Enable debug mode to get detailed information about request processing: + +```bash +dotnet-http GET api.example.com/data --debug +``` + +Debug mode provides: +- Detailed request/response logging +- Middleware execution information +- Error stack traces +- Performance timing +- Configuration details + +## Offline Mode (Request Preview) + +Preview requests without sending them: + +```bash +# Preview a single request +dotnet-http POST api.example.com/users name=John --offline + +# Preview HTTP file execution +dotnet-http exec requests.http --offline + +# Preview with debug information +dotnet-http POST api.example.com/users name=John --debug --offline +``` + +Offline mode is useful for: +- Validating request structure +- Checking JSON formatting +- Verifying headers and parameters +- Testing variable substitution + +## Common Issues + +### 1. Installation Problems + +#### Tool Not Found After Installation + +**Problem**: `dotnet-http: command not found` + +**Solutions**: +```bash +# Check if tool is installed +dotnet tool list --global + +# Verify .NET tools path is in PATH +echo $PATH | grep -q "$HOME/.dotnet/tools" || echo "Tools path not in PATH" + +# Add to shell profile (bash/zsh) +echo 'export PATH="$PATH:$HOME/.dotnet/tools"' >> ~/.bashrc +source ~/.bashrc + +# Reinstall if corrupted +dotnet tool uninstall --global dotnet-httpie +dotnet tool install --global dotnet-httpie +``` + +#### Permission Denied + +**Problem**: Permission issues during installation + +**Solutions**: +```bash +# Check permissions on tools directory +ls -la ~/.dotnet/tools/ + +# Fix permissions if needed +chmod +x ~/.dotnet/tools/dotnet-http + +# Install for current user only +dotnet tool install --global dotnet-httpie --tool-path ~/.local/bin +``` + +### 2. Request Issues + +#### SSL/TLS Certificate Errors + +**Problem**: SSL certificate validation failures + +**Solutions**: +```bash +# Skip SSL verification (development only) +dotnet-http GET https://self-signed-site.com --verify=no + +# Use custom CA certificate +dotnet-http GET https://internal-api.company.com \ + --ca-cert /path/to/ca-certificate.pem + +# Check SSL certificate details +openssl s_client -connect api.example.com:443 -servername api.example.com +``` + +#### Connection Timeouts + +**Problem**: Requests timing out + +**Solutions**: +```bash +# Increase timeout (if supported) +dotnet-http GET api.example.com/slow-endpoint --timeout 60 + +# Check network connectivity +ping api.example.com +curl -I api.example.com + +# Test with simpler request first +dotnet-http GET api.example.com/health +``` + +#### Authentication Failures + +**Problem**: 401 Unauthorized responses + +**Debug Steps**: +```bash +# Check token validity +echo $JWT_TOKEN | base64 -d + +# Verify token format +dotnet-http GET api.example.com/verify \ + Authorization:"Bearer $JWT_TOKEN" \ + --debug + +# Test with minimal request +dotnet-http GET api.example.com/public + +# Check header format +dotnet-http GET httpbin.org/headers \ + Authorization:"Bearer $JWT_TOKEN" +``` + +### 3. JSON and Data Issues + +#### JSON Parsing Errors + +**Problem**: Invalid JSON in request body + +**Debug**: +```bash +# Preview JSON structure +dotnet-http POST api.example.com/users \ + name=John \ + age:=30 \ + tags:='["dev", "api"]' \ + --offline + +# Validate JSON manually +echo '{"name": "John", "age": 30}' | jq . + +# Use file for complex JSON +cat > user.json << EOF +{ + "name": "John", + "age": 30, + "skills": ["C#", "JavaScript"] +} +EOF + +dotnet-http POST api.example.com/users @user.json +``` + +#### Character Encoding Issues + +**Problem**: Special characters not handled correctly + +**Solutions**: +```bash +# Specify charset +dotnet-http POST api.example.com/data \ + Content-Type:"application/json; charset=utf-8" \ + message="Hello 世界" + +# Use file with proper encoding +echo '{"message": "Hello 世界"}' > data.json +dotnet-http POST api.example.com/data @data.json +``` + +### 4. File Execution Issues + +#### File Not Found + +**Problem**: HTTP files not loading + +**Debug**: +```bash +# Check file exists and permissions +ls -la requests.http + +# Use absolute path +dotnet-http exec /full/path/to/requests.http + +# Check current directory +pwd +dotnet-http exec ./requests.http +``` + +#### Variable Substitution Failures + +**Problem**: Variables not being replaced + +**Debug**: +```bash +# Check environment file +cat http-client.env.json + +# Verify environment selection +dotnet-http exec requests.http --env development --debug + +# Test variable syntax +# Good: {{baseUrl}} +# Bad: {baseUrl} or $baseUrl +``` + +#### Request Reference Errors + +**Problem**: Cannot reference previous responses + +**Debug**: +```bash +# Ensure request names are defined +# @name createUser +POST {{baseUrl}}/users + +# Reference with correct syntax +GET {{baseUrl}}/users/{{createUser.response.body.id}} + +# Check response structure +dotnet-http exec requests.http --debug --offline +``` + +## Debugging Tools and Techniques + +### 1. Network Analysis + +#### Using tcpdump/Wireshark + +```bash +# Capture network traffic (Linux/macOS) +sudo tcpdump -i any -w capture.pcap host api.example.com + +# Run your request +dotnet-http GET api.example.com/data + +# Analyze capture with Wireshark or tcpdump +tcpdump -r capture.pcap -A +``` + +#### Using curl for comparison + +```bash +# Compare with curl behavior +curl -v https://api.example.com/data \ + -H "Authorization: Bearer $TOKEN" + +# Convert to dotnet-httpie equivalent +dotnet-http GET api.example.com/data \ + Authorization:"Bearer $TOKEN" \ + --debug +``` + +### 2. Response Analysis + +#### JSON Processing + +```bash +# Pretty print JSON response +dotnet-http GET api.example.com/users | jq . + +# Extract specific fields +dotnet-http GET api.example.com/users | jq '.users[0].id' + +# Validate JSON schema +dotnet-http GET api.example.com/users | jq 'type' +``` + +#### Header Analysis + +```bash +# Show response headers only +dotnet-http HEAD api.example.com/data + +# Check specific headers +dotnet-http GET httpbin.org/headers | jq '.headers' + +# Trace header propagation +dotnet-http GET api.example.com/data \ + X-Trace-ID:"$(uuidgen)" \ + --debug +``` + +### 3. Performance Debugging + +#### Response Time Analysis + +```bash +# Measure response time +time dotnet-http GET api.example.com/data + +# Multiple requests for average +for i in {1..10}; do + time dotnet-http GET api.example.com/data >/dev/null +done +``` + +#### Memory and Resource Usage + +```bash +# Monitor resource usage +top -p $(pgrep dotnet-http) + +# Memory usage +ps aux | grep dotnet-http +``` + +## Error Analysis + +### HTTP Status Codes + +#### 4xx Client Errors + +```bash +# 400 Bad Request +dotnet-http POST api.example.com/users \ + invalid-json \ + --debug # Check request format + +# 401 Unauthorized +dotnet-http GET api.example.com/protected \ + --debug # Check authentication + +# 403 Forbidden +dotnet-http GET api.example.com/admin \ + Authorization:"Bearer $USER_TOKEN" \ + --debug # Check permissions + +# 404 Not Found +dotnet-http GET api.example.com/nonexistent \ + --debug # Check URL + +# 429 Too Many Requests +dotnet-http GET api.example.com/data \ + --debug # Check rate limiting +``` + +#### 5xx Server Errors + +```bash +# 500 Internal Server Error +dotnet-http POST api.example.com/users \ + name=John \ + --debug # Check server logs + +# 502 Bad Gateway +dotnet-http GET api.example.com/data \ + --debug # Check proxy/load balancer + +# 503 Service Unavailable +dotnet-http GET api.example.com/health \ + --debug # Check service status +``` + +### Error Response Analysis + +```bash +# Capture error details +ERROR_RESPONSE=$(dotnet-http GET api.example.com/error --body 2>&1) +echo "$ERROR_RESPONSE" | jq . + +# Check error structure +dotnet-http GET api.example.com/error | jq '{error, message, code}' +``` + +## Logging and Monitoring + +### Request Logging + +```bash +# Log all requests to file +dotnet-http GET api.example.com/data --debug > request.log 2>&1 + +# Structured logging +dotnet-http GET api.example.com/data 2>&1 | \ + grep -E "(Request|Response|Error)" > structured.log +``` + +### Automated Health Checks + +```bash +#!/bin/bash +# health-monitor.sh + +LOG_FILE="/var/log/api-health.log" + +check_endpoint() { + local endpoint=$1 + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + + if dotnet-http GET "$endpoint" --check-status >/dev/null 2>&1; then + echo "[$timestamp] OK: $endpoint" >> "$LOG_FILE" + return 0 + else + echo "[$timestamp] FAIL: $endpoint" >> "$LOG_FILE" + return 1 + fi +} + +# Monitor multiple endpoints +check_endpoint "https://api.example.com/health" +check_endpoint "https://api.example.com/status" +check_endpoint "https://auth.example.com/health" +``` + +## Docker Debugging + +### Container Issues + +```bash +# Run with debug output +docker run --rm weihanli/dotnet-httpie:latest \ + GET api.example.com/data --debug + +# Check container logs +docker run --name httpie-debug weihanli/dotnet-httpie:latest \ + GET api.example.com/data +docker logs httpie-debug +docker rm httpie-debug + +# Interactive debugging +docker run -it --rm weihanli/dotnet-httpie:latest /bin/sh +``` + +### Network Issues in Docker + +```bash +# Test network connectivity +docker run --rm weihanli/dotnet-httpie:latest \ + GET httpbin.org/get + +# Use host network +docker run --rm --network host \ + weihanli/dotnet-httpie:latest GET localhost:3000/api + +# Check DNS resolution +docker run --rm weihanli/dotnet-httpie:latest \ + nslookup api.example.com +``` + +## Environment Debugging + +### Environment Variables + +```bash +# Check all environment variables +env | grep -i http +env | grep -i api + +# Verify specific variables +echo "API_TOKEN: $API_TOKEN" +echo "BASE_URL: $BASE_URL" + +# Debug variable expansion +dotnet-http GET "$BASE_URL/data" \ + Authorization:"Bearer $API_TOKEN" \ + --debug +``` + +### Configuration Files + +```bash +# Check HTTP client environment +cat http-client.env.json | jq . + +# Validate JSON syntax +jq empty http-client.env.json && echo "Valid JSON" || echo "Invalid JSON" + +# Check file permissions +ls -la http-client.env.json +``` + +## Performance Troubleshooting + +### Slow Requests + +```bash +# Add timing information +time dotnet-http GET api.example.com/slow-endpoint + +# Profile with curl for comparison +time curl -s https://api.example.com/slow-endpoint >/dev/null + +# Check DNS resolution time +time nslookup api.example.com + +# Test with different endpoints +time dotnet-http GET httpbin.org/delay/5 +``` + +### Memory Issues + +```bash +# Monitor memory usage during large requests +dotnet-http GET api.example.com/large-dataset & +PID=$! +while kill -0 $PID 2>/dev/null; do + ps -p $PID -o pid,vsz,rss,comm + sleep 1 +done +``` + +## Advanced Debugging + +### Custom Middleware Debugging + +If you're developing custom middleware: + +```bash +# Enable detailed middleware logging +dotnet-http GET api.example.com/data \ + --debug \ + -v # Verbose mode if available +``` + +### Source Code Debugging + +```bash +# Clone repository for local debugging +git clone https://github.com/WeihanLi/dotnet-httpie.git +cd dotnet-httpie + +# Build in debug mode +dotnet build -c Debug + +# Run with debugger +dotnet run --project src/HTTPie -- GET api.example.com/data --debug +``` + +## Getting Help + +### Information to Include in Bug Reports + +When reporting issues, include: + +1. **Version information**: + ```bash + dotnet-http --version + dotnet --version + ``` + +2. **Command that failed**: + ```bash + dotnet-http GET api.example.com/data --debug + ``` + +3. **Expected vs actual behavior** + +4. **Error messages** (full output with `--debug`) + +5. **Environment details**: + - Operating system + - .NET version + - Docker version (if using Docker) + +### Community Resources + +- [GitHub Issues](https://github.com/WeihanLi/dotnet-httpie/issues) +- [GitHub Discussions](https://github.com/WeihanLi/dotnet-httpie/discussions) +- [Stack Overflow](https://stackoverflow.com/questions/tagged/dotnet-httpie) + +### Self-Help Checklist + +Before asking for help: + +- [ ] Tried with `--debug` flag +- [ ] Tested with `--offline` to check request structure +- [ ] Verified authentication tokens/keys +- [ ] Checked network connectivity +- [ ] Tested with a simple request first +- [ ] Reviewed relevant documentation +- [ ] Searched existing issues + +## Next Steps + +- Review [performance tips](performance-tips.md) for optimization +- Check [common use cases](examples/common-use-cases.md) for working examples +- Explore [advanced features](middleware-system.md) for complex scenarios \ No newline at end of file diff --git a/docs/articles/docker-usage.md b/docs/articles/docker-usage.md new file mode 100644 index 0000000..5c56517 --- /dev/null +++ b/docs/articles/docker-usage.md @@ -0,0 +1,428 @@ +# Docker Usage + +dotnet-httpie is available as a Docker image, making it easy to use in containerized environments, CI/CD pipelines, and systems without .NET installed. + +## Docker Image + +The official Docker image is available at: `weihanli/dotnet-httpie` + +### Available Tags + +- `latest` - Latest stable release +- `preview` - Latest preview/pre-release version +- `0.12.0` - Specific version tags + +## Quick Start + +### Pull the Image + +```bash +docker pull weihanli/dotnet-httpie:latest +``` + +### Basic Usage + +```bash +# Simple GET request +docker run --rm weihanli/dotnet-httpie:latest httpbin.org/get + +# POST with data +docker run --rm weihanli/dotnet-httpie:latest POST httpbin.org/post name=John age:=30 + +# With headers +docker run --rm weihanli/dotnet-httpie:latest GET httpbin.org/headers Authorization:"Bearer token" +``` + +## Common Usage Patterns + +### Interactive Usage + +```bash +# Create an alias for easier usage +alias http='docker run --rm -i weihanli/dotnet-httpie:latest' + +# Now use it like the installed version +http GET httpbin.org/get +http POST httpbin.org/post name=John +``` + +### With Local Files + +Mount local directories to access files: + +```bash +# Mount current directory +docker run --rm -v $(pwd):/workspace -w /workspace \ + weihanli/dotnet-httpie:latest exec requests.http + +# Mount specific directory +docker run --rm -v /path/to/files:/files \ + weihanli/dotnet-httpie:latest exec /files/api-tests.http +``` + +### Environment Variables + +Pass environment variables to the container: + +```bash +# Single environment variable +docker run --rm -e API_TOKEN="your-token" \ + weihanli/dotnet-httpie:latest GET api.example.com/protected \ + Authorization:"Bearer $API_TOKEN" + +# Multiple environment variables +docker run --rm \ + -e API_BASE_URL="https://api.example.com" \ + -e API_TOKEN="your-token" \ + weihanli/dotnet-httpie:latest GET "$API_BASE_URL/users" \ + Authorization:"Bearer $API_TOKEN" + +# Environment file +docker run --rm --env-file .env \ + weihanli/dotnet-httpie:latest GET api.example.com/data +``` + +## File Operations + +### Executing HTTP Files + +```bash +# Mount and execute HTTP file +docker run --rm -v $(pwd):/workspace -w /workspace \ + weihanli/dotnet-httpie:latest exec tests/api.http + +# With environment +docker run --rm -v $(pwd):/workspace -w /workspace \ + weihanli/dotnet-httpie:latest exec tests/api.http --env production +``` + +### File Downloads + +```bash +# Download to mounted volume +docker run --rm -v $(pwd)/downloads:/downloads \ + weihanli/dotnet-httpie:latest GET httpbin.org/image/png \ + --download --output /downloads/image.png +``` + +### Upload Files + +```bash +# Upload file from mounted volume +docker run --rm -v $(pwd):/workspace -w /workspace \ + weihanli/dotnet-httpie:latest POST api.example.com/upload \ + @data.json +``` + +## Networking + +### Host Network + +Access services running on the host: + +```bash +# Access localhost services (Linux) +docker run --rm --network host \ + weihanli/dotnet-httpie:latest GET localhost:3000/api/health + +# Access host services (macOS/Windows) +docker run --rm \ + weihanli/dotnet-httpie:latest GET host.docker.internal:3000/api/health +``` + +### Custom Networks + +```bash +# Create network +docker network create api-test-network + +# Run API server in network +docker run -d --name api-server --network api-test-network my-api-image + +# Test API using dotnet-httpie in same network +docker run --rm --network api-test-network \ + weihanli/dotnet-httpie:latest GET api-server:3000/health +``` + +## CI/CD Integration + +### GitHub Actions + +```yaml +name: API Tests +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Test API Health + run: | + docker run --rm --network host \ + weihanli/dotnet-httpie:latest GET localhost:3000/health + + - name: Run API Test Suite + run: | + docker run --rm -v ${{ github.workspace }}:/workspace -w /workspace \ + weihanli/dotnet-httpie:latest exec tests/api-suite.http --env testing + + - name: Test with Authentication + env: + API_TOKEN: ${{ secrets.API_TOKEN }} + run: | + docker run --rm -e API_TOKEN \ + weihanli/dotnet-httpie:latest GET api.example.com/protected \ + Authorization:"Bearer $API_TOKEN" +``` + +### Azure DevOps + +```yaml +stages: +- stage: ApiTests + jobs: + - job: RunTests + pool: + vmImage: 'ubuntu-latest' + steps: + - script: | + docker run --rm -v $(System.DefaultWorkingDirectory):/workspace -w /workspace \ + weihanli/dotnet-httpie:latest exec tests/integration.http --env $(Environment) + displayName: 'Run Integration Tests' + env: + API_TOKEN: $(ApiToken) +``` + +### GitLab CI + +```yaml +test-api: + image: docker:latest + services: + - docker:dind + script: + - docker run --rm -v $PWD:/workspace -w /workspace + weihanli/dotnet-httpie:latest exec tests/api.http --env $CI_ENVIRONMENT_NAME + variables: + API_TOKEN: $API_TOKEN +``` + +## Docker Compose Integration + +### Basic Setup + +```yaml +# docker-compose.yml + +services: + api: + image: my-api:latest + ports: + - "3000:3000" + environment: + - NODE_ENV=development + + api-tests: + image: weihanli/dotnet-httpie:latest + depends_on: + - api + volumes: + - ./tests:/tests + command: exec /tests/api-suite.http + environment: + - API_BASE_URL=http://api:3000 +``` + +### Health Checks + +```yaml +version: '3.8' + +services: + api: + image: my-api:latest + healthcheck: + test: ["CMD", "docker", "run", "--rm", "--network", "container:my-api", + "weihanli/dotnet-httpie:latest", "GET", "localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 +``` + +## Advanced Scenarios + +### Multi-Stage Testing + +```bash +# Test development environment +docker run --rm -v $(pwd):/workspace -w /workspace \ + weihanli/dotnet-httpie:latest exec tests/smoke.http --env development + +# Test staging environment +docker run --rm -v $(pwd):/workspace -w /workspace \ + -e API_BASE_URL="https://staging.api.example.com" \ + weihanli/dotnet-httpie:latest exec tests/full-suite.http --env staging + +# Test production environment +docker run --rm -v $(pwd):/workspace -w /workspace \ + -e API_BASE_URL="https://api.example.com" \ + weihanli/dotnet-httpie:latest exec tests/health-check.http --env production +``` + +### Proxy Testing + +```bash +# Test through proxy +docker run --rm \ + -e HTTP_PROXY="http://proxy.company.com:8080" \ + -e HTTPS_PROXY="http://proxy.company.com:8080" \ + weihanli/dotnet-httpie:latest GET api.example.com/data +``` + +## Shell Scripts and Automation + +### Bash Scripts + +```bash +#!/bin/bash +# api-test.sh + +set -e + +API_BASE_URL="${API_BASE_URL:-http://localhost:3000}" +DOCKER_IMAGE="weihanli/dotnet-httpie:latest" + +echo "Testing API at $API_BASE_URL..." + +# Health check +echo "Checking API health..." +docker run --rm $DOCKER_IMAGE GET "$API_BASE_URL/health" + +# Login to get token (JSON data, not Basic Auth) +echo "Testing login..." +TOKEN=$(docker run --rm $DOCKER_IMAGE POST "$API_BASE_URL/auth/login" \ + username=testuser password=testpass --body | jq -r '.token') + +# Protected endpoint test with Bearer token +echo "Testing protected endpoint..." +docker run --rm $DOCKER_IMAGE GET "$API_BASE_URL/protected" \ + Authorization:"Bearer $TOKEN" + +# Basic Auth test (HTTP Basic Authentication) +echo "Testing Basic Auth..." +docker run --rm $DOCKER_IMAGE GET "$API_BASE_URL/basic-protected" \ + --auth testuser:testpass + +echo "All tests passed!" +``` + +### PowerShell Scripts + +```powershell +# api-test.ps1 + +param( + [string]$ApiBaseUrl = "http://localhost:3000", + [string]$Environment = "development" +) + +$dockerImage = "weihanli/dotnet-httpie:latest" + +Write-Host "Testing API at $ApiBaseUrl..." + +# Run test suite +docker run --rm -v "${PWD}:/workspace" -w /workspace ` + -e API_BASE_URL=$ApiBaseUrl ` + $dockerImage exec tests/api-suite.http --env $Environment + +Write-Host "Tests completed!" +``` + +## Configuration + +### Custom Configuration + +```bash +# Mount custom configuration +docker run --rm -v $(pwd)/config:/config \ + -e DOTNET_HTTPIE_CONFIG="/config/httpie.json" \ + weihanli/dotnet-httpie:latest GET api.example.com/data +``` + +### SSL Certificates + +```bash +# Mount custom CA certificates +docker run --rm -v $(pwd)/certs:/certs \ + -e SSL_CERT_DIR="/certs" \ + weihanli/dotnet-httpie:latest GET https://internal-api.company.com/data +``` + +## Troubleshooting + +### Debug Mode + +```bash +# Enable debug output +docker run --rm \ + weihanli/dotnet-httpie:latest GET httpbin.org/get --debug +``` + +### Offline Mode + +```bash +# Preview requests without sending +docker run --rm -v $(pwd):/workspace -w /workspace \ + weihanli/dotnet-httpie:latest exec tests/api.http --offline +``` + +### Container Logs + +```bash +# Run with verbose output +docker run --rm \ + weihanli/dotnet-httpie:latest GET httpbin.org/get --verbose + +# Save logs +docker run --rm \ + weihanli/dotnet-httpie:latest GET httpbin.org/get > request.log 2>&1 +``` + +## Performance Considerations + +### Image Size + +The dotnet-httpie Docker image is optimized for size using: +- Multi-stage builds +- Alpine Linux base (where applicable) +- AOT compilation for reduced runtime dependencies + +### Caching + +```bash +# Pre-pull image for faster execution +docker pull weihanli/dotnet-httpie:latest + +# Use specific version for consistency +docker run --rm weihanli/dotnet-httpie:0.12.0 GET httpbin.org/get +``` + +## Best Practices + +1. **Use specific image tags** in production environments +2. **Mount volumes efficiently** - only mount what you need +3. **Use environment variables** for configuration +4. **Leverage Docker networks** for service-to-service communication +5. **Clean up containers** with `--rm` flag +6. **Pre-pull images** in CI/CD for faster execution +7. **Use multi-stage testing** for different environments +8. **Secure sensitive data** using Docker secrets or external secret management + +## Next Steps + +- Set up [CI/CD integration](ci-cd-integration.md) with Docker +- Learn about [environment configuration](environment-variables.md) +- Explore [advanced examples](examples/integrations.md) with Docker +- Review [troubleshooting guide](debugging.md) for common issues \ No newline at end of file diff --git a/docs/articles/examples/common-use-cases.md b/docs/articles/examples/common-use-cases.md new file mode 100644 index 0000000..095c6c4 --- /dev/null +++ b/docs/articles/examples/common-use-cases.md @@ -0,0 +1,612 @@ +# Common Use Cases + +This guide provides practical examples for the most common scenarios when using dotnet-httpie. + +## API Development & Testing + +### REST API CRUD Operations + +```bash +# Users API example +BASE_URL="https://api.example.com" +TOKEN="your-jwt-token" + +# Create user +dotnet-http POST $BASE_URL/users \ + Authorization:"Bearer $TOKEN" \ + Content-Type:"application/json" \ + name="John Doe" \ + email="john@example.com" \ + role="user" + +# Get all users +dotnet-http GET $BASE_URL/users \ + Authorization:"Bearer $TOKEN" + +# Get specific user +dotnet-http GET $BASE_URL/users/123 \ + Authorization:"Bearer $TOKEN" + +# Update user +dotnet-http PUT $BASE_URL/users/123 \ + Authorization:"Bearer $TOKEN" \ + name="John Smith" \ + email="john.smith@example.com" + +# Partial update +dotnet-http PATCH $BASE_URL/users/123 \ + Authorization:"Bearer $TOKEN" \ + email="newemail@example.com" + +# Delete user +dotnet-http DELETE $BASE_URL/users/123 \ + Authorization:"Bearer $TOKEN" +``` + +### GraphQL API + +```bash +# GraphQL query +dotnet-http POST https://api.github.com/graphql \ + Authorization:"Bearer $GITHUB_TOKEN" \ + query='query { viewer { login name } }' + +# GraphQL mutation +dotnet-http POST https://api.github.com/graphql \ + Authorization:"Bearer $GITHUB_TOKEN" \ + query='mutation { createIssue(input: {repositoryId: "repo-id", title: "Bug report", body: "Description"}) { issue { id title } } }' +``` + +## Authentication Patterns + +### JWT Authentication + +```bash +# Login and get token +LOGIN_RESPONSE=$(dotnet-http POST api.example.com/auth/login \ + username="admin" \ + password="password" \ + --body) + +TOKEN=$(echo $LOGIN_RESPONSE | jq -r '.token') + +# Use token for protected requests +dotnet-http GET api.example.com/protected \ + Authorization:"Bearer $TOKEN" +``` + +### API Key Authentication + +```bash +# Header-based API key +dotnet-http GET api.example.com/data \ + X-API-Key:"your-api-key" + +# Query parameter API key +dotnet-http GET api.example.com/data \ + api_key==your-api-key +``` + +### Basic Authentication + +```bash +# Basic auth +dotnet-http GET api.example.com/secure \ + Authorization:"Basic $(echo -n 'username:password' | base64)" + +# Or with HTTPie-style auth +dotnet-http GET api.example.com/secure \ + --auth username:password +``` + +### OAuth 2.0 + +```bash +# Get access token +TOKEN_RESPONSE=$(dotnet-http POST oauth.example.com/token \ + grant_type="client_credentials" \ + client_id="your-client-id" \ + client_secret="your-client-secret" \ + scope="read write" \ + --body) + +ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token') + +# Use access token +dotnet-http GET api.example.com/protected \ + Authorization:"Bearer $ACCESS_TOKEN" +``` + +## File Operations + +### File Uploads + +```bash +# Single file upload +dotnet-http POST api.example.com/upload \ + Authorization:"Bearer $TOKEN" \ + --multipart \ + file@/path/to/document.pdf \ + description="Important document" + +# Multiple file upload +dotnet-http POST api.example.com/batch-upload \ + --multipart \ + doc1@/path/to/file1.pdf \ + doc2@/path/to/file2.pdf \ + metadata@/path/to/metadata.json + +# Image upload with metadata +dotnet-http POST api.example.com/images \ + --multipart \ + image@/path/to/photo.jpg \ + title="Vacation Photo" \ + tags:='["vacation", "travel", "beach"]' \ + public:=true +``` + +### File Downloads + +```bash +# Download file +dotnet-http GET api.example.com/files/document.pdf \ + Authorization:"Bearer $TOKEN" \ + --download + +# Download with custom filename +dotnet-http GET api.example.com/exports/data.csv \ + --download \ + --output "$(date +%Y%m%d)-export.csv" + +# Download large files with progress +dotnet-http GET api.example.com/large-file.zip \ + --download \ + --progress +``` + +## Data Processing + +### JSON Processing with jq + +```bash +# Extract specific fields +USER_ID=$(dotnet-http POST api.example.com/users name="John" --body | jq -r '.id') + +# Filter arrays +dotnet-http GET api.example.com/users | jq '.users[] | select(.active == true)' + +# Transform data +dotnet-http GET api.example.com/users | jq '.users | map({id, name, email})' + +# Count results +COUNT=$(dotnet-http GET api.example.com/users | jq '.users | length') +echo "Total users: $COUNT" +``` + +### Pagination + +```bash +# Fetch all pages +page=1 +all_data="[]" + +while true; do + response=$(dotnet-http GET api.example.com/users page==$page limit==50 --body) + data=$(echo $response | jq '.data') + + if [ "$(echo $data | jq 'length')" -eq 0 ]; then + break + fi + + all_data=$(echo $all_data $data | jq -s 'add') + ((page++)) +done + +echo $all_data | jq . +``` + +## CI/CD Integration + +### Health Checks + +```bash +#!/bin/bash +# health-check.sh + +check_service() { + local service_url=$1 + local service_name=$2 + + echo "Checking $service_name..." + + if dotnet-http GET $service_url/health --check-status; then + echo "✓ $service_name is healthy" + return 0 + else + echo "✗ $service_name is unhealthy" + return 1 + fi +} + +# Check multiple services +check_service "https://api.example.com" "API Service" +check_service "https://auth.example.com" "Auth Service" +check_service "https://cache.example.com" "Cache Service" +``` + +### Deployment Verification + +```bash +#!/bin/bash +# verify-deployment.sh + +ENVIRONMENT=${1:-staging} +BASE_URL="https://$ENVIRONMENT.api.example.com" + +echo "Verifying deployment in $ENVIRONMENT..." + +# Check API version +VERSION=$(dotnet-http GET $BASE_URL/version --body | jq -r '.version') +echo "API Version: $VERSION" + +# Run smoke tests +dotnet-http exec tests/smoke-tests.http --env $ENVIRONMENT + +# Check critical endpoints +dotnet-http GET $BASE_URL/health +dotnet-http GET $BASE_URL/metrics +dotnet-http GET $BASE_URL/ready + +echo "Deployment verification complete!" +``` + +### Load Testing + +```bash +#!/bin/bash +# load-test.sh + +URL="https://api.example.com/endpoint" +CONCURRENT=10 +REQUESTS=100 + +echo "Running load test: $REQUESTS requests with $CONCURRENT concurrent users" + +# Create temporary file for results +RESULTS_FILE=$(mktemp) + +# Run concurrent requests +for i in $(seq 1 $CONCURRENT); do + ( + for j in $(seq 1 $((REQUESTS / CONCURRENT))); do + start_time=$(date +%s%N) + + if dotnet-http GET $URL > /dev/null 2>&1; then + end_time=$(date +%s%N) + duration=$(((end_time - start_time) / 1000000)) + echo "SUCCESS,$duration" >> $RESULTS_FILE + else + echo "FAILURE,0" >> $RESULTS_FILE + fi + done + ) & +done + +wait + +# Analyze results +total=$(wc -l < $RESULTS_FILE) +success=$(grep "SUCCESS" $RESULTS_FILE | wc -l) +failures=$((total - success)) +avg_time=$(grep "SUCCESS" $RESULTS_FILE | cut -d, -f2 | awk '{sum+=$1} END {print sum/NR}') + +echo "Results:" +echo " Total requests: $total" +echo " Successful: $success" +echo " Failed: $failures" +echo " Success rate: $(( success * 100 / total ))%" +echo " Average response time: ${avg_time}ms" + +rm $RESULTS_FILE +``` + +## API Testing Workflows + +### End-to-End Testing + +```http +# tests/e2e-workflow.http +@baseUrl = https://api.example.com +@contentType = application/json + +### + +# @name login +POST {{baseUrl}}/auth/login +Content-Type: {{contentType}} + +{ + "username": "testuser", + "password": "testpass" +} + +### + +# @name createUser +POST {{baseUrl}}/users +Authorization: Bearer {{login.response.body.token}} +Content-Type: {{contentType}} + +{ + "name": "Test User", + "email": "test@example.com", + "role": "user" +} + +### + +# @name getUser +GET {{baseUrl}}/users/{{createUser.response.body.id}} +Authorization: Bearer {{login.response.body.token}} + +### + +# @name updateUser +PUT {{baseUrl}}/users/{{createUser.response.body.id}} +Authorization: Bearer {{login.response.body.token}} +Content-Type: {{contentType}} + +{ + "name": "Updated Test User", + "email": "updated@example.com" +} + +### + +# @name deleteUser +DELETE {{baseUrl}}/users/{{createUser.response.body.id}} +Authorization: Bearer {{login.response.body.token}} +``` + +### Contract Testing + +```bash +#!/bin/bash +# contract-test.sh + +echo "Running API contract tests..." + +# Test required fields +response=$(dotnet-http POST api.example.com/users name="Test" email="test@example.com" --body) + +# Validate response structure +echo $response | jq -e '.id' > /dev/null || { echo "Missing id field"; exit 1; } +echo $response | jq -e '.name' > /dev/null || { echo "Missing name field"; exit 1; } +echo $response | jq -e '.email' > /dev/null || { echo "Missing email field"; exit 1; } +echo $response | jq -e '.created_at' > /dev/null || { echo "Missing created_at field"; exit 1; } + +# Validate data types +[ "$(echo $response | jq -r '.id | type')" = "string" ] || { echo "ID should be string"; exit 1; } +[ "$(echo $response | jq -r '.name | type')" = "string" ] || { echo "Name should be string"; exit 1; } + +echo "✓ All contract tests passed" +``` + +## Microservices Testing + +### Service Discovery + +```bash +#!/bin/bash +# test-microservices.sh + +SERVICES=("user-service" "order-service" "payment-service" "notification-service") +BASE_URL="https://api.example.com" + +for service in "${SERVICES[@]}"; do + echo "Testing $service..." + + # Health check + dotnet-http GET $BASE_URL/$service/health + + # Version check + VERSION=$(dotnet-http GET $BASE_URL/$service/version --body | jq -r '.version') + echo "$service version: $VERSION" + + # Basic functionality test + case $service in + "user-service") + dotnet-http GET $BASE_URL/users/1 + ;; + "order-service") + dotnet-http GET $BASE_URL/orders limit==5 + ;; + "payment-service") + dotnet-http GET $BASE_URL/payments/methods + ;; + "notification-service") + dotnet-http GET $BASE_URL/notifications/templates + ;; + esac + + echo "✓ $service test completed" + echo +done +``` + +### Cross-Service Integration + +```http +# tests/cross-service.http +@baseUrl = https://api.example.com + +### + +# @name createUser +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "name": "Integration Test User", + "email": "integration@example.com" +} + +### + +# @name createOrder +POST {{baseUrl}}/orders +Content-Type: application/json + +{ + "user_id": "{{createUser.response.body.id}}", + "items": [ + {"product_id": "prod-123", "quantity": 2}, + {"product_id": "prod-456", "quantity": 1} + ] +} + +### + +# @name processPayment +POST {{baseUrl}}/payments +Content-Type: application/json + +{ + "order_id": "{{createOrder.response.body.id}}", + "amount": "{{createOrder.response.body.total}}", + "method": "credit_card", + "card_token": "test-token-123" +} + +### + +# @name sendNotification +POST {{baseUrl}}/notifications +Content-Type: application/json + +{ + "user_id": "{{createUser.response.body.id}}", + "type": "order_confirmation", + "data": { + "order_id": "{{createOrder.response.body.id}}", + "payment_id": "{{processPayment.response.body.id}}" + } +} +``` + +## Development Workflows + +### Local Development + +```bash +#!/bin/bash +# dev-setup.sh + +echo "Setting up local development environment..." + +# Start local services +docker-compose up -d + +# Wait for services to be ready +sleep 10 + +# Seed test data +dotnet-http POST localhost:3000/api/seed + +# Run initial tests +dotnet-http exec tests/local-smoke-tests.http --env development + +echo "Development environment ready!" +``` + +### API Documentation Testing + +```bash +#!/bin/bash +# test-api-docs.sh + +# Extract API endpoints from OpenAPI spec +ENDPOINTS=$(curl -s https://api.example.com/openapi.json | jq -r '.paths | keys[]') + +echo "Testing API endpoints from documentation..." + +for endpoint in $ENDPOINTS; do + # Convert OpenAPI path to actual URL + url="https://api.example.com${endpoint//\{[^}]*\}/123}" + + echo "Testing: $url" + + if dotnet-http GET "$url" > /dev/null 2>&1; then + echo "✓ $endpoint" + else + echo "✗ $endpoint" + fi +done +``` + +## Monitoring & Alerting + +### Uptime Monitoring + +```bash +#!/bin/bash +# uptime-monitor.sh + +SERVICES=( + "https://api.example.com/health" + "https://auth.example.com/health" + "https://cdn.example.com/status" +) + +for service in "${SERVICES[@]}"; do + if ! dotnet-http GET "$service" --check-status; then + # Send alert + dotnet-http POST "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK" \ + text="🚨 Service down: $service" + fi +done +``` + +### Performance Monitoring + +```bash +#!/bin/bash +# perf-monitor.sh + +ENDPOINT="https://api.example.com/users" +THRESHOLD=1000 # milliseconds + +start_time=$(date +%s%N) +dotnet-http GET "$ENDPOINT" > /dev/null +end_time=$(date +%s%N) + +duration=$(((end_time - start_time) / 1000000)) + +if [ $duration -gt $THRESHOLD ]; then + echo "⚠️ Slow response detected: ${duration}ms (threshold: ${THRESHOLD}ms)" + + # Send performance alert + dotnet-http POST "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK" \ + text="⚠️ Slow API response: $ENDPOINT took ${duration}ms" +fi +``` + +## Best Practices Summary + +1. **Use environment variables** for configuration and secrets +2. **Implement proper error handling** in scripts +3. **Create reusable test suites** with .http files +4. **Combine with other tools** like jq for data processing +5. **Use meaningful names** for saved requests +6. **Document your API tests** with comments +7. **Version control your test files** alongside your code +8. **Implement retry logic** for flaky endpoints +9. **Use offline mode** to preview requests +10. **Monitor and alert** on API health and performance + +## Next Steps + +- Explore [API testing scenarios](api-testing.md) for more advanced patterns +- Learn about [integration examples](integrations.md) with other tools +- Check out [performance tips](../performance-tips.md) for optimization +- Review [debugging guide](../debugging.md) for troubleshooting \ No newline at end of file diff --git a/docs/articles/file-execution.md b/docs/articles/file-execution.md new file mode 100644 index 0000000..546501d --- /dev/null +++ b/docs/articles/file-execution.md @@ -0,0 +1,479 @@ +# File Execution + +dotnet-httpie can execute HTTP requests from `.http` and `.rest` files, making it perfect for API testing, documentation, and automation. + +## Overview + +The `exec` command allows you to run HTTP requests defined in files, supporting: +- Standard `.http` and `.rest` file formats +- Variable substitution +- Environment-specific configurations +- Request chaining and referencing + +## Basic Usage + +### Execute Single File + +```bash +dotnet-http exec requests.http +``` + +### Execute with Environment + +```bash +dotnet-http exec requests.http --env production +``` + +### Execute Specific Request Type + +```bash +dotnet-http exec requests.http --type http +dotnet-http exec curl-commands.curl --type curl +``` + +## HTTP File Format + +### Basic Request + +```http +# Get user information +GET https://api.example.com/users/123 +Authorization: Bearer your-token +``` + +### Multiple Requests + +```http +# Get all users +GET https://api.example.com/users +Authorization: Bearer your-token + +### + +# Create new user +POST https://api.example.com/users +Content-Type: application/json +Authorization: Bearer your-token + +{ + "name": "John Doe", + "email": "john@example.com" +} + +### + +# Update user +PUT https://api.example.com/users/123 +Content-Type: application/json +Authorization: Bearer your-token + +{ + "name": "John Smith", + "email": "john.smith@example.com" +} +``` + +### Request with Variables + +```http +@baseUrl = https://api.example.com +@token = your-bearer-token + +# Get user +GET {{baseUrl}}/users/123 +Authorization: Bearer {{token}} + +### + +# Create user with dynamic data +POST {{baseUrl}}/users +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "User {{$randomInt}}", + "email": "user{{$randomInt}}@example.com", + "timestamp": "{{$datetime iso8601}}" +} +``` + +### Named Requests + +```http +@baseUrl = https://api.example.com + +### + +# @name getUser +GET {{baseUrl}}/users/123 + +### + +# @name createUser +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "name": "John Doe", + "email": "john@example.com" +} +``` + +## Environment Files + +### HTTP Client Environment File + +Create `http-client.env.json`: + +```json +{ + "development": { + "baseUrl": "http://localhost:3000", + "apiKey": "dev-api-key" + }, + "staging": { + "baseUrl": "https://staging-api.example.com", + "apiKey": "staging-api-key" + }, + "production": { + "baseUrl": "https://api.example.com", + "apiKey": "prod-api-key" + } +} +``` + +### Using Environment Variables + +```http +# This will use variables from the specified environment +GET {{baseUrl}}/users +X-API-Key: {{apiKey}} +``` + +Execute with specific environment: + +```bash +dotnet-http exec api-requests.http --env production +``` + +## Variable Types + +### Built-in Variables + +```http +# Random values +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "id": "{{$uuid}}", + "name": "User {{$randomInt}}", + "email": "user{{$randomInt}}@example.com", + "timestamp": "{{$datetime iso8601}}", + "created": "{{$timestamp}}" +} +``` + +### Environment Variables + +```http +# Access system environment variables +GET {{baseUrl}}/secure +Authorization: Bearer {{$env API_TOKEN}} +``` + +### Custom Variables + +```http +@userId = 123 +@apiVersion = v2 + +GET {{baseUrl}}/{{apiVersion}}/users/{{userId}} +``` + +## Request Referencing + +Reference responses from previous requests: + +```http +# @name login +POST {{baseUrl}}/auth/login +Content-Type: application/json + +{ + "username": "admin", + "password": "password" +} + +### + +# Use token from login response +GET {{baseUrl}}/protected/data +Authorization: Bearer {{login.response.body.token}} + +### + +# Reference request headers +GET {{baseUrl}}/audit +X-Original-Request-Id: {{login.request.headers.X-Request-ID}} +``` + +## Curl File Execution + +dotnet-httpie can also execute curl commands from files: + +### Curl File Format + +```bash +# file: api-calls.curl + +# Get user data +curl -X GET "https://api.example.com/users/123" \ + -H "Authorization: Bearer token" \ + -H "Accept: application/json" + +# Create new user +curl -X POST "https://api.example.com/users" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer token" \ + -d '{ + "name": "John Doe", + "email": "john@example.com" + }' +``` + +### Execute Curl File + +```bash +dotnet-http exec api-calls.curl --type curl +``` + +## Advanced Features + +### Request Chaining + +```http +# @name createUser +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "name": "John Doe", + "email": "john@example.com" +} + +### + +# @name getUserProfile +# @depends createUser +GET {{baseUrl}}/users/{{createUser.response.body.id}}/profile + +### + +# @name updateProfile +# @depends getUserProfile +PUT {{baseUrl}}/users/{{createUser.response.body.id}}/profile +Content-Type: application/json + +{ + "bio": "Updated bio", + "avatar": "{{getUserProfile.response.body.avatar}}" +} +``` + +## Testing and Validation + +### Response Assertions + +```http +GET {{baseUrl}}/users/123 + +# Test response +# @test status === 200 +# @test response.body.name === "John Doe" +# @test response.headers["content-type"] includes "application/json" +``` + +### Schema Validation + +```http +POST {{baseUrl}}/users +Content-Type: application/json + +{ + "name": "John Doe", + "email": "john@example.com" +} + +# Validate response against JSON schema +# @schema user-schema.json +``` + +## Debugging File Execution + +### Debug Mode + +```bash +dotnet-http exec requests.http --debug +``` + +### Offline Mode (Preview) + +```bash +dotnet-http exec requests.http --offline +``` + +This shows what requests would be sent without actually executing them. + +### Verbose Output + +```bash +dotnet-http exec requests.http --verbose +``` + +## Organization Strategies + +### Project Structure + +``` +project/ +├── api-tests/ +│ ├── auth/ +│ │ ├── login.http +│ │ └── logout.http +│ ├── users/ +│ │ ├── create-user.http +│ │ ├── get-user.http +│ │ └── update-user.http +│ └── http-client.env.json +├── environments/ +│ ├── development.env.json +│ ├── staging.env.json +│ └── production.env.json +└── scripts/ + ├── setup.http + ├── cleanup.http + └── health-check.http +``` + +### File Naming Conventions + +- Use descriptive names: `create-user.http`, `get-order-details.http` +- Group by feature: `auth/`, `users/`, `orders/` +- Use environment prefixes: `dev-setup.http`, `prod-health.http` + +## CI/CD Integration + +### GitHub Actions Example + +```yaml +name: API Tests +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '8.0' + - name: Install dotnet-httpie + run: dotnet tool install --global dotnet-httpie + - name: Run API tests + run: dotnet-http exec tests/api-tests.http --env testing +``` + +### Azure DevOps Example + +```yaml +steps: +- task: DotNetCoreCLI@2 + displayName: 'Install dotnet-httpie' + inputs: + command: 'custom' + custom: 'tool' + arguments: 'install --global dotnet-httpie' + +- script: dotnet-http exec api-tests/health-check.http --env $(Environment) + displayName: 'Run Health Check' +``` + +## Best Practices + +1. **Use environment files** for different deployment environments +2. **Name your requests** for better organization and referencing +3. **Group related requests** in the same file +4. **Use variables** instead of hardcoding values +5. **Add comments** to explain complex requests +6. **Validate responses** where critical +7. **Test offline first** to verify request structure +8. **Version control** your HTTP files alongside your code + +## Examples + +### Complete API Test Suite + +```http +@baseUrl = https://api.example.com +@contentType = application/json + +### + +# @name healthCheck +GET {{baseUrl}}/health + +### + +# @name authenticate +POST {{baseUrl}}/auth/login +Content-Type: {{contentType}} + +{ + "username": "testuser", + "password": "testpass" +} + +### + +# @name createUser +POST {{baseUrl}}/users +Authorization: Bearer {{authenticate.response.body.token}} +Content-Type: {{contentType}} + +{ + "name": "Test User", + "email": "test@example.com" +} + +### + +# @name getUser +GET {{baseUrl}}/users/{{createUser.response.body.id}} +Authorization: Bearer {{authenticate.response.body.token}} + +### + +# @name updateUser +PUT {{baseUrl}}/users/{{createUser.response.body.id}} +Authorization: Bearer {{authenticate.response.body.token}} +Content-Type: {{contentType}} + +{ + "name": "Updated Test User" +} + +### + +# @name deleteUser +DELETE {{baseUrl}}/users/{{createUser.response.body.id}} +Authorization: Bearer {{authenticate.response.body.token}} +``` + +## Next Steps + +- Learn about [variable substitution](variables.md) in detail +- Explore [request referencing](request-referencing.md) patterns +- Set up [CI/CD integration](ci-cd-integration.md) with HTTP files +- Check [common use cases](examples/common-use-cases.md) for more examples \ No newline at end of file diff --git a/docs/articles/http-requests.md b/docs/articles/http-requests.md new file mode 100644 index 0000000..8d4b854 --- /dev/null +++ b/docs/articles/http-requests.md @@ -0,0 +1,336 @@ +# HTTP Requests + +This guide covers how to make various types of HTTP requests using dotnet-httpie. + +## HTTP Methods + +dotnet-httpie supports all standard HTTP methods. If no method is specified, GET is used by default. + +### GET Requests + +```bash +# Simple GET +dotnet-http httpbin.org/get + +# GET with query parameters +dotnet-http httpbin.org/get name==John age==30 + +# GET with headers +dotnet-http httpbin.org/get Authorization:"Bearer token" +``` + +### POST Requests + +```bash +# JSON POST request +dotnet-http POST httpbin.org/post name=John email=john@example.com + +# Form data POST request +dotnet-http POST httpbin.org/post --form name=John email=john@example.com + +# Raw data POST request +dotnet-http POST httpbin.org/post --raw "Custom raw data" +``` + +### PUT Requests + +```bash +# Update resource +dotnet-http PUT api.example.com/users/123 name="John Smith" email="john.smith@example.com" + +# Replace entire resource +dotnet-http PUT api.example.com/users/123 @user.json +``` + +### PATCH Requests + +```bash +# Partial update +dotnet-http PATCH api.example.com/users/123 email="newemail@example.com" +``` + +### DELETE Requests + +```bash +# Delete resource +dotnet-http DELETE api.example.com/users/123 + +# Delete with confirmation header +dotnet-http DELETE api.example.com/users/123 X-Confirm:"yes" +``` + +### HEAD Requests + +```bash +# Get headers only +dotnet-http HEAD httpbin.org/get +``` + +### OPTIONS Requests + +```bash +# Check allowed methods +dotnet-http OPTIONS api.example.com/users +``` + +## Request URLs + +### Full URLs + +```bash +dotnet-http https://api.example.com/users +dotnet-http http://localhost:3000/api/data +``` + +### Shortened Local URLs + +```bash +# These are equivalent to http://localhost:PORT +dotnet-http :3000/api/users +dotnet-http localhost:3000/api/users +``` + +### URL with Parameters + +```bash +# Query parameters are added automatically +dotnet-http api.example.com/search q==httpie type==tool +# Results in: api.example.com/search?q=httpie&type=tool +``` + +## Request Headers + +### Standard Headers + +```bash +# Authorization +dotnet-http api.example.com/protected Authorization:"Bearer jwt-token" + +# Content-Type +dotnet-http POST api.example.com/data Content-Type:"application/xml" @data.xml + +# User-Agent +dotnet-http api.example.com/get User-Agent:"MyApp/1.0" + +# Accept +dotnet-http api.example.com/data Accept:"application/json" +``` + +### Custom Headers + +```bash +# API keys +dotnet-http api.example.com/data X-API-Key:"your-api-key" + +# Custom headers +dotnet-http api.example.com/data X-Custom-Header:"custom-value" +``` + +### Multiple Headers + +```bash +dotnet-http api.example.com/data \ + Authorization:"Bearer token" \ + X-API-Key:"api-key" \ + User-Agent:"MyApp/1.0" \ + Accept:"application/json" +``` + +## Request Body + +### JSON Body (Default) + +```bash +# Simple JSON +dotnet-http POST api.example.com/users name=John age:=30 active:=true + +# Nested JSON +dotnet-http POST api.example.com/users name=John address[city]=Seattle address[country]=USA + +# Array values +dotnet-http POST api.example.com/users name=John tags:='["developer", "dotnet"]' + +# Raw JSON objects +dotnet-http POST api.example.com/users profile:='{"name": "John", "age": 30}' +``` + +### Form Data + +```bash +# URL-encoded form data +dotnet-http POST httpbin.org/post --form name=John email=john@example.com +``` + +### Raw Data + +```bash +# Send raw string +dotnet-http POST api.example.com/webhook --raw "Raw webhook payload" + +# Send from stdin +echo "data" | dotnet-http POST api.example.com/data +``` + +## Complex JSON Structures + +### Nested Objects + +```bash +dotnet-http POST api.example.com/users \ + name=John \ + address[street]="123 Main St" \ + address[city]=Seattle \ + address[zipcode]:=98101 +``` + +### Arrays + +```bash +# Array of strings +dotnet-http POST api.example.com/users name=John skills:='["C#", "JavaScript", "Python"]' + +# Array of numbers +dotnet-http POST api.example.com/data values:='[1, 2, 3, 4, 5]' + +# Array of objects +dotnet-http POST api.example.com/batch items:='[{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]' +``` + +### Boolean and Null Values + +```bash +# Boolean values +dotnet-http POST api.example.com/users name=John active:=true verified:=false + +# Null values +dotnet-http POST api.example.com/users name=John middle_name:=null + +# Numbers +dotnet-http POST api.example.com/users name=John age:=30 salary:=50000.50 +``` + +## Response Handling + +### View Full Response + +```bash +# Default: shows headers and body +dotnet-http httpbin.org/get +``` + +### Body Only + +```bash +# Show only response body +dotnet-http httpbin.org/get --body +``` + +### Headers Only + +```bash +# Show only response headers +dotnet-http HEAD httpbin.org/get +``` + +### Save Response + +```bash +# Save to file +dotnet-http httpbin.org/get > response.json + +# Download files +dotnet-http httpbin.org/image/png --download +``` + +## Error Handling + +### HTTP Error Codes + +```bash +# dotnet-httpie shows HTTP errors clearly +dotnet-http httpbin.org/status/404 +dotnet-http httpbin.org/status/500 +``` + +### Timeout Configuration + +```bash +# Set request timeout (if supported by middleware) +dotnet-http api.example.com/slow-endpoint --timeout 30 +``` + +## Advanced Features + +### Follow Redirects + +```bash +# Automatically follow redirects +dotnet-http httpbin.org/redirect/3 --follow +``` + +### Ignore SSL Errors + +```bash +# For development/testing only +dotnet-http https://self-signed.badssl.com/ --verify=no +``` + +### Proxy Support + +```bash +# Use proxy +dotnet-http httpbin.org/get --proxy http://proxy.example.com:8080 +``` + +## Examples with Real APIs + +### GitHub API + +```bash +# Get user info +dotnet-http api.github.com/users/octocat + +# Get repositories (with authentication) +dotnet-http api.github.com/user/repos Authorization:"token your-token" + +# Create issue +dotnet-http POST api.github.com/repos/owner/repo/issues \ + Authorization:"token your-token" \ + title="Bug report" \ + body="Description of the bug" +``` + +### REST API CRUD Operations + +```bash +# Create +dotnet-http POST api.example.com/articles \ + title="My Article" \ + content="Article content" \ + published:=false + +# Read +dotnet-http GET api.example.com/articles/123 + +# Update +dotnet-http PUT api.example.com/articles/123 \ + title="Updated Article" \ + published:=true + +# Delete +dotnet-http DELETE api.example.com/articles/123 +``` + +## Best Practices + +1. **Use meaningful names** for your request files and variables +2. **Store sensitive data** like API keys in environment variables +3. **Use --offline mode** to preview requests before sending +4. **Combine with jq** for JSON processing: `dotnet-http api.example.com/data | jq .` +5. **Use files for complex data** instead of inline JSON for better maintainability + +## Next Steps + +- Learn about [authentication methods](authentication.md) +- Explore [file execution](file-execution.md) for repeatable requests +- Check out [variable substitution](variables.md) for dynamic requests diff --git a/docs/articles/installation.md b/docs/articles/installation.md new file mode 100644 index 0000000..05bb92f --- /dev/null +++ b/docs/articles/installation.md @@ -0,0 +1,145 @@ +# Installation + +This guide covers how to install dotnet-httpie on different platforms and environments. + +## Prerequisites + +- .NET SDK 8.0 or later +- Internet connection for package installation + +## Global Tool Installation (Recommended) + +### Install Latest Stable Version + +```bash +dotnet tool update --global dotnet-httpie +``` + +### Install Latest Preview Version + +```bash +dotnet tool update --global dotnet-httpie --prerelease +``` + +### Install Specific Version + +```bash +dotnet tool install --global dotnet-httpie --version 0.12.0 +``` + +## Alternative Installation Methods + +### Option 2: Pre-built Binaries + +Download platform-specific executables from [GitHub Releases](https://github.com/WeihanLi/dotnet-httpie/releases): + +- **Windows**: `dotnet-httpie-win-x64.exe` +- **Linux**: `dotnet-httpie-linux-x64` +- **macOS**: `dotnet-httpie-osx-x64` + +Extract and add to your system PATH for global access. + +### Option 3: Docker + +See the [Docker Usage Guide](docker-usage.md) for containerized usage. + +## Verification + +After installation, verify that dotnet-httpie is working correctly: + +```bash +dotnet-http --version +``` + +You should see output similar to: +``` +dotnet-httpie/0.12.0 (.NET; HTTPie-like) +``` + +## Docker Installation + +If you prefer using Docker instead of installing globally: + +### Pull Latest Image + +```bash +docker pull weihanli/dotnet-httpie:latest +``` + +### Use Without Installation + +```bash +docker run --rm weihanli/dotnet-httpie:latest --help +``` + +## Updating + +### Update Global Tool + +```bash +dotnet tool update --global dotnet-httpie +``` + +### Update to Preview Version + +```bash +dotnet tool update --global dotnet-httpie --prerelease +``` + +## Uninstallation + +### Remove Global Tool + +```bash +dotnet tool uninstall --global dotnet-httpie +``` + +### Remove Docker Image + +```bash +docker rmi weihanli/dotnet-httpie:latest +``` + +## Troubleshooting Installation + +### Common Issues + +1. **Permission Denied**: Make sure you have proper permissions to install global tools +2. **PATH Issues**: Ensure the .NET tools directory is in your PATH +3. **Old .NET Version**: Verify you have .NET 8.0 or later installed + +### Check .NET Version + +```bash +dotnet --version +``` + +### Check Installed Tools + +```bash +dotnet tool list --global +``` + +### Reinstall if Corrupted + +```bash +dotnet tool uninstall --global dotnet-httpie +dotnet tool install --global dotnet-httpie +``` + +## Platform-Specific Notes + +### Windows +- Tools are installed to `%USERPROFILE%\.dotnet\tools` +- May require restart of command prompt/PowerShell + +### macOS/Linux +- Tools are installed to `~/.dotnet/tools` +- May need to restart terminal session + +### CI/CD Environments +See [CI/CD Integration](ci-cd-integration.md) for specific setup instructions for continuous integration environments. + +## Next Steps + +Once installed, continue with the [Quick Start Guide](quick-start.md) to begin using dotnet-httpie. \ No newline at end of file diff --git a/docs/articles/quick-start.md b/docs/articles/quick-start.md new file mode 100644 index 0000000..ed03b1e --- /dev/null +++ b/docs/articles/quick-start.md @@ -0,0 +1,178 @@ +# Quick Start Guide + +Get up and running with dotnet-httpie in minutes! This guide shows you the essential commands to start making HTTP requests. + +## Your First Request + +Make a simple GET request: + +```bash +dotnet-http httpbin.org/get +``` + +This will send a GET request to httpbin.org and display the response. + +## Basic Command Structure + +``` +dotnet-http [flags] [METHOD] URL [ITEM [ITEM]] +``` + +Where: +- `flags`: Optional command flags (e.g., `--offline`, `--debug`) +- `METHOD`: HTTP method (GET, POST, PUT, DELETE, etc.) - defaults to GET +- `URL`: The target URL +- `ITEM`: Request items (query parameters, headers, data) + +## Request Item Types + +dotnet-httpie supports three types of request items: + +| Type | Syntax | Example | Description | +|------|--------|---------|-------------| +| Query Parameters | `name==value` | `search==dotnet` | URL query parameters | +| Headers | `name:value` | `Authorization:Bearer token` | HTTP headers | +| JSON Data | `name=value` | `title=hello` | JSON request body fields | +| Raw JSON | `name:=value` | `age:=25` | Raw JSON values (numbers, booleans, objects) | + +## Common Examples + +### GET with Query Parameters + +```bash +dotnet-http httpbin.org/get search==httpie limit==10 +``` + +### POST with JSON Data + +```bash +dotnet-http POST httpbin.org/post title=Hello body="World" +``` + +### Custom Headers + +```bash +dotnet-http httpbin.org/headers Authorization:"Bearer your-token" User-Agent:"MyApp/1.0" +``` + +### Mixed Request Types + +```bash +dotnet-http POST api.example.com/users \ + Authorization:"Bearer token" \ + name="John Doe" \ + age:=30 \ + active:=true \ + search==users +``` + +## Working with APIs + +### RESTful API Example + +```bash +# List users +dotnet-http GET api.example.com/users + +# Get specific user +dotnet-http GET api.example.com/users/123 + +# Create new user +dotnet-http POST api.example.com/users name="John" email="john@example.com" + +# Update user +dotnet-http PUT api.example.com/users/123 name="John Smith" + +# Delete user +dotnet-http DELETE api.example.com/users/123 +``` + +### JSON API with Authentication + +```bash +dotnet-http POST api.example.com/posts \ + Authorization:"Bearer your-jwt-token" \ + Content-Type:"application/json" \ + title="My Post" \ + content="This is my post content" \ + published:=true +``` + +## Useful Flags + +### Offline Mode (Preview Request) + +```bash +dotnet-http POST api.example.com/data name=test --offline +``` + +This shows what request would be sent without actually sending it. + +### Debug Mode + +```bash +dotnet-http httpbin.org/get --debug +``` + +Enables detailed logging and debugging information. + +### Response Body Only + +```bash +dotnet-http httpbin.org/get --body +``` + +Shows only the response body, useful for piping to other tools. + +## File Operations + +### Execute HTTP Files + +```bash +dotnet-http exec requests.http +``` + +### Download Files + +```bash +dotnet-http httpbin.org/image/png --download +``` + +## Docker Usage + +If you're using the Docker version: + +```bash +# Basic request +docker run --rm weihanli/dotnet-httpie:latest httpbin.org/get + +# With data +docker run --rm weihanli/dotnet-httpie:latest POST httpbin.org/post name=test +``` + +## Local Development + +For local APIs, you can use shortened URLs: + +```bash +# Instead of http://localhost:5000/api/users +dotnet-http :5000/api/users + +# Or +dotnet-http localhost:5000/api/users +``` + +## Next Steps + +Now that you're familiar with the basics: + +1. Learn about [advanced request data types](request-data-types.md) +2. Explore [file execution capabilities](file-execution.md) +3. Set up [authentication](authentication.md) for your APIs +4. Check out [common use cases](examples/common-use-cases.md) + +## Need Help? + +- Use `dotnet-http --help` for command-line help +- See [debugging guide](debugging.md) for troubleshooting +- Check [examples](examples/common-use-cases.md) for more usage patterns \ No newline at end of file diff --git a/docs/articles/reference/command-line-options.md b/docs/articles/reference/command-line-options.md new file mode 100644 index 0000000..326a6a4 --- /dev/null +++ b/docs/articles/reference/command-line-options.md @@ -0,0 +1,376 @@ +# Command Line Options Reference + +This comprehensive reference covers all command line options and flags available in dotnet-httpie. + +## Command Syntax + +``` +dotnet-http [global-flags] [METHOD] URL [request-items...] [request-flags] +``` + +## Global Flags + +### Help and Version + +| Flag | Description | Example | +|------|-------------|---------| +| `--help`, `-h` | Show help information | `dotnet-http --help` | +| `--version` | Show version information | `dotnet-http --version` | + +### Debug and Logging + +| Flag | Description | Example | +|------|-------------|---------| +| `--debug` | Enable debug mode with detailed logging | `dotnet-http GET api.example.com --debug` | +| `--verbose`, `-v` | Enable verbose output | `dotnet-http GET api.example.com --verbose` | +| `--quiet`, `-q` | Suppress non-error output | `dotnet-http GET api.example.com --quiet` | + +### Request Preview + +| Flag | Description | Example | +|------|-------------|---------| +| `--offline` | Preview request without sending | `dotnet-http POST api.example.com --offline` | +| `--print` | Specify what to print (request/response) | `dotnet-http GET api.example.com --print=HhBb` | + +## HTTP Methods + +All standard HTTP methods are supported: + +```bash +# GET (default) +dotnet-http GET api.example.com/users +dotnet-http api.example.com/users # GET is default when there's no body data parameter + +# POST +dotnet-http POST api.example.com/users + +# PUT +dotnet-http PUT api.example.com/users/123 + +# PATCH +dotnet-http PATCH api.example.com/users/123 + +# DELETE +dotnet-http DELETE api.example.com/users/123 + +# HEAD +dotnet-http HEAD api.example.com/users + +# OPTIONS +dotnet-http OPTIONS api.example.com/users +``` + +## Request Flags + +### Authentication + +| Flag | Description | Example | +|------|-------------|---------| +| `--auth`, `-a` | Basic authentication | `dotnet-http GET api.example.com --auth user:pass` | +| `--auth-type` | Authentication type | `dotnet-http GET api.example.com --auth-type bearer` | + +### Request Body + +| Flag | Description | Example | +|------|-------------|---------| +| `--json`, `-j` | Force JSON content type | `dotnet-http POST api.example.com --json` | +| `--form`, `-f` | Send as form data | `dotnet-http POST api.example.com --form` | +| `--raw` | Send raw data | `dotnet-http POST api.example.com --raw "text data"` | + +### File Operations + +| Flag | Description | Example | +|------|-------------|---------| +| `--download`, `-d` | Download response to file | `dotnet-http GET api.example.com/file.pdf --download` | +| `--output`, `-o` | Save response to specific file | `dotnet-http GET api.example.com/data --output data.json` | +| `--continue`, `-C` | Resume interrupted download | `dotnet-http GET api.example.com/large.zip --download --continue` | + + + +## Execute Command Options + +The `exec` command has its own set of options: + +```bash +dotnet-http exec [options] [file-path] +``` + +### Execute Flags + +| Flag | Description | Example | +|------|-------------|---------| +| `--env` | Environment to use | `dotnet-http exec requests.http --env production` | +| `--type`, `-t` | Script type (http/curl) | `dotnet-http exec script.curl --type curl` | +| `--debug` | Debug mode for execution | `dotnet-http exec requests.http --debug` | +| `--offline` | Preview execution without sending | `dotnet-http exec requests.http --offline` | + +### Supported Script Types + +| Type | Description | File Extensions | +|------|-------------|-----------------| +| `http` | HTTP request files | `.http`, `.rest` | +| `curl` | cURL command files | `.curl`, `.sh` | + +## Request Item Types + +### Query Parameters + +```bash +# Syntax: name==value +dotnet-http GET api.example.com/search query==httpie limit==10 +``` + +### Headers + +```bash +# Syntax: name:value +dotnet-http GET api.example.com/data Authorization:"Bearer token" +``` + +### JSON Fields + +```bash +# Syntax: name=value (creates JSON) +dotnet-http POST api.example.com/users name=John email=john@example.com +``` + +### Raw JSON Values + +```bash +# Syntax: name:=value (typed JSON) +dotnet-http POST api.example.com/users age:=30 active:=true +``` + + + +## Output Formats + +### Response Output Control + +```bash +# Default: headers + body +dotnet-http GET api.example.com/users + +# Headers only +dotnet-http HEAD api.example.com/users + +# Body only +dotnet-http GET api.example.com/users --body + +# Specific components with --print +dotnet-http GET api.example.com/users --print=HhBb +``` + +### Print Option Values + +| Code | Component | +|------|-----------| +| `H` | Request headers | +| `B` | Request body | +| `h` | Response headers | +| `b` | Response body | +| `p` | Request/Response properties | + +## Exit Codes + +| Code | Meaning | +|------|---------| +| `0` | Success | +| `1` | Generic error | +| `2` | Request timeout | +| `3` | Too many redirects | +| `4` | HTTP 4xx error | +| `5` | HTTP 5xx error | +| `6` | Network error | + +## Examples by Category + +### Basic Requests + +```bash +# Simple GET +dotnet-http GET httpbin.org/get + +# POST with data +dotnet-http POST httpbin.org/post name=John age:=30 + +# Custom headers +dotnet-http GET httpbin.org/headers User-Agent:"MyApp/1.0" +``` + +### Authentication Examples + +```bash +# Bearer token +dotnet-http GET api.example.com/protected Authorization:"Bearer token" + +# Basic auth +dotnet-http GET api.example.com/secure --auth user:pass + +# API key +dotnet-http GET api.example.com/data X-API-Key:"key123" +``` + +### File Operations + +```bash +# Download file +dotnet-http GET api.example.com/report.pdf --download + +# Send file as body +dotnet-http POST api.example.com/data @data.json +``` + +### Form Data + +```bash +# URL-encoded form +dotnet-http POST httpbin.org/post --form name=John email=john@example.com +``` + +### Response Handling + +```bash +# Save response +dotnet-http GET api.example.com/data --output response.json + +# Body only to stdout +dotnet-http GET api.example.com/data --body + +# Headers only +dotnet-http HEAD api.example.com/data +``` + +### Network Options + +```bash +# With proxy +dotnet-http GET api.example.com/data --proxy http://proxy:8080 + +# Custom timeout +dotnet-http GET api.example.com/slow --timeout 60 + +# Skip SSL verification +dotnet-http GET https://self-signed.local --verify=no +``` + +### Debug and Development + +```bash +# Debug mode +dotnet-http GET api.example.com/data --debug + +# Preview request +dotnet-http POST api.example.com/users name=John --offline + +# Verbose output +dotnet-http GET api.example.com/data --verbose +``` + +## Advanced Usage Patterns + +### Environment-Specific Requests + +```bash +# Development +dotnet-http GET localhost:3000/api/users --debug + +# Staging +dotnet-http GET staging-api.example.com/users \ + Authorization:"Bearer $STAGING_TOKEN" + +# Production +dotnet-http GET api.example.com/users \ + Authorization:"Bearer $PROD_TOKEN" \ + --timeout 30 +``` + +### Conditional Requests + +```bash +# Check status code +if dotnet-http GET api.example.com/health --check-status; then + echo "API is healthy" +fi + +# With error handling +dotnet-http GET api.example.com/data || echo "Request failed" +``` + +### Scripting Integration + +```bash +# Extract data for further processing +USER_ID=$(dotnet-http POST api.example.com/users name=John --body | jq -r '.id') +dotnet-http GET "api.example.com/users/$USER_ID/profile" + +# Batch operations +for user in alice bob charlie; do + dotnet-http POST api.example.com/users name="$user" +done +``` + +## Migration from Other Tools + +### From cURL + +```bash +# cURL +curl -X POST https://api.example.com/users \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer token" \ + -d '{"name": "John", "age": 30}' + +# dotnet-httpie equivalent +dotnet-http POST api.example.com/users \ + Authorization:"Bearer token" \ + name=John \ + age:=30 +``` + +### From HTTPie + +```bash +# HTTPie +http POST api.example.com/users Authorization:"Bearer token" name=John age:=30 + +# dotnet-httpie (very similar) +dotnet-http POST api.example.com/users Authorization:"Bearer token" name=John age:=30 +``` + +## Platform-Specific Notes + +### Windows + +```powershell +# PowerShell escaping +dotnet-http POST api.example.com/users name=John tags:='[\"web\", \"api\"]' + +# Command Prompt escaping +dotnet-http POST api.example.com/users name=John tags:="[\"web\", \"api\"]" +``` + +### macOS/Linux + +```bash +# Bash/Zsh (standard escaping) +dotnet-http POST api.example.com/users name=John tags:='["web", "api"]' + +# Fish shell +dotnet-http POST api.example.com/users name=John tags:=\'["web", "api"]\' +``` + +## Performance Tips + +1. **Use `--body` for piping**: Avoids header processing overhead +2. **Reuse connections**: Use session management where available +3. **Minimize debug output**: Only use `--debug` when troubleshooting +4. **Optimize JSON**: Use `:=` for numbers/booleans instead of strings +5. **Batch requests**: Use file execution for multiple related requests + +## Next Steps + +- Review [common use cases](../examples/common-use-cases.md) for practical applications +- Learn about [file execution](../file-execution.md) for advanced workflows +- Check [debugging guide](../debugging.md) for troubleshooting +- Explore [Docker usage](../docker-usage.md) for containerized environments \ No newline at end of file diff --git a/docs/articles/request-data-types.md b/docs/articles/request-data-types.md new file mode 100644 index 0000000..ea1d558 --- /dev/null +++ b/docs/articles/request-data-types.md @@ -0,0 +1,460 @@ +# Request Data Types + +This guide explains the different ways to structure and send data with your HTTP requests using dotnet-httpie. + +## Overview + +dotnet-httpie supports multiple request data formats and provides intuitive syntax for different data types: + +- **Query Parameters**: `name==value` +- **Headers**: `name:value` +- **JSON Fields**: `name=value` +- **Raw JSON Values**: `name:=value` + +## Query Parameters + +Query parameters are appended to the URL and use the `==` syntax. + +### Basic Query Parameters + +```bash +# Single parameter +dotnet-http httpbin.org/get search==httpie + +# Multiple parameters +dotnet-http httpbin.org/get search==httpie lang==en page==1 + +# URL encoding is automatic +dotnet-http httpbin.org/get query=="hello world" special=="chars!@#" +``` + +### Arrays in Query Parameters + +```bash +# Multiple values for same parameter +dotnet-http httpbin.org/get tags==javascript tags==web tags==api + +# Results in: ?tags=javascript&tags=web&tags=api +``` + +### Empty Values + +```bash +# Empty parameter +dotnet-http httpbin.org/get empty== + +# Null parameter (omitted) +dotnet-http httpbin.org/get param==null +``` + +## HTTP Headers + +Headers use the `:` syntax and control how requests are processed. + +### Common Headers + +```bash +# Authorization +dotnet-http httpbin.org/headers Authorization:"Bearer jwt-token" + +# Content-Type +dotnet-http POST httpbin.org/post Content-Type:"application/xml" + +# User-Agent +dotnet-http httpbin.org/headers User-Agent:"MyApp/1.0" + +# Accept +dotnet-http httpbin.org/headers Accept:"application/json, text/plain" + +# API Keys +dotnet-http api.example.com/data X-API-Key:"your-api-key" +``` + +### Custom Headers + +```bash +# Multiple custom headers +dotnet-http api.example.com/webhook \ + X-Webhook-Source:"github" \ + X-Signature:"sha256=signature" \ + X-Event-Type:"push" +``` + +### Header Values with Spaces + +```bash +# Quote values containing spaces +dotnet-http httpbin.org/headers User-Agent:"My Application v1.0" +``` + +## JSON Request Body + +### Simple JSON Fields + +Using `=` creates JSON fields automatically: + +```bash +dotnet-http POST httpbin.org/post name=John email=john@example.com + +# Generates: +# { +# "name": "John", +# "email": "john@example.com" +# } +``` + +### Data Types + +#### Strings (Default) + +```bash +dotnet-http POST httpbin.org/post title="Hello World" description="A test post" +``` + +#### Numbers + +```bash +# Integers +dotnet-http POST httpbin.org/post age:=30 count:=100 + +# Floats +dotnet-http POST httpbin.org/post price:=19.99 rating:=4.5 +``` + +#### Booleans + +```bash +dotnet-http POST httpbin.org/post active:=true verified:=false published:=true +``` + +#### Null Values + +```bash +dotnet-http POST httpbin.org/post middle_name:=null optional_field:=null +``` + +#### Arrays + +```bash +# Array of strings +dotnet-http POST httpbin.org/post tags:='["javascript", "web", "api"]' + +# Array of numbers +dotnet-http POST httpbin.org/post scores:='[95, 87, 92, 78]' + +# Array of objects +dotnet-http POST httpbin.org/post items:='[{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]' +``` + +#### Objects + +```bash +# Nested objects +dotnet-http POST httpbin.org/post profile:='{"name": "John", "age": 30, "skills": ["C#", "JavaScript"]}' + +# Complex nested structure +dotnet-http POST httpbin.org/post config:='{"database": {"host": "localhost", "port": 5432}, "features": {"auth": true, "cache": false}}' +``` + +## Nested JSON Structures + +### Bracket Notation + +```bash +# Nested objects using bracket notation +dotnet-http POST httpbin.org/post \ + user[name]=John \ + user[email]=john@example.com \ + user[address][street]="123 Main St" \ + user[address][city]=Seattle \ + user[address][zipcode]:=98101 + +# Generates: +# { +# "user": { +# "name": "John", +# "email": "john@example.com", +# "address": { +# "street": "123 Main St", +# "city": "Seattle", +# "zipcode": 98101 +# } +# } +# } +``` + +### Array Elements + +```bash +# Array with indexed elements +dotnet-http POST httpbin.org/post \ + items[0][name]=First \ + items[0][value]:=100 \ + items[1][name]=Second \ + items[1][value]:=200 + +# Generates: +# { +# "items": [ +# {"name": "First", "value": 100}, +# {"name": "Second", "value": 200} +# ] +# } +``` + +## Form Data + +### URL-Encoded Forms + +```bash +# Use --form flag for application/x-www-form-urlencoded +dotnet-http POST httpbin.org/post --form name=John email=john@example.com + +# Mixed with other options +dotnet-http POST httpbin.org/post --form \ + name=John \ + age=30 \ + Authorization:"Bearer token" +``` + +### Multipart Forms + +```bash +# Use --multipart for multipart/form-data +dotnet-http POST httpbin.org/post --multipart \ + name=John \ + file@/path/to/document.pdf + +# Multiple files +dotnet-http POST httpbin.org/post --multipart \ + name=John \ + avatar@/path/to/avatar.jpg \ + resume@/path/to/resume.pdf +``` + +## File Uploads + +### Send File as Body + +```bash +# Send entire file as request body +dotnet-http POST api.example.com/upload @/path/to/data.json + +# With content type +dotnet-http POST api.example.com/upload \ + Content-Type:"application/json" \ + @/path/to/data.json +``` + +### File in Multipart Form + +```bash +# File as form field +dotnet-http POST api.example.com/upload --multipart \ + description="My document" \ + file@/path/to/document.pdf +``` + +### Multiple Files + +```bash +dotnet-http POST api.example.com/batch-upload --multipart \ + batch_name="Document Batch" \ + doc1@/path/to/file1.pdf \ + doc2@/path/to/file2.pdf \ + metadata@/path/to/metadata.json +``` + +## Raw Data + +### Raw String Data + +```bash +# Send raw text +dotnet-http POST api.example.com/webhook \ + Content-Type:"text/plain" \ + --raw "This is raw text data" + +# Raw JSON (alternative to field syntax) +dotnet-http POST api.example.com/data \ + Content-Type:"application/json" \ + --raw '{"name": "John", "age": 30}' + +# Raw XML +dotnet-http POST api.example.com/xml \ + Content-Type:"application/xml" \ + --raw 'John30' +``` + +### Data from Stdin + +```bash +# Pipe data from command +echo '{"message": "Hello"}' | dotnet-http POST api.example.com/data + +# From file via stdin +cat data.json | dotnet-http POST api.example.com/upload + +# From other tools +curl -s api.example.com/export | dotnet-http POST api.example.com/import +``` + +## Data Type Examples + +### E-commerce API + +```bash +# Create product +dotnet-http POST api.shop.com/products \ + Authorization:"Bearer token" \ + name="Laptop Computer" \ + description="High-performance laptop" \ + price:=999.99 \ + in_stock:=true \ + categories:='["electronics", "computers"]' \ + specifications:='{"cpu": "Intel i7", "ram": "16GB", "storage": "512GB SSD"}' \ + tags==electronics tags==computers +``` + +### User Registration + +```bash +# Complex user object +dotnet-http POST api.example.com/users \ + personal[first_name]=John \ + personal[last_name]=Doe \ + personal[email]=john.doe@example.com \ + personal[phone]="+1-555-0123" \ + address[street]="123 Main Street" \ + address[city]=Seattle \ + address[state]=WA \ + address[zipcode]:=98101 \ + address[country]=USA \ + preferences[newsletter]:=true \ + preferences[notifications]:=false \ + role=user \ + active:=true +``` + +### API Configuration + +```bash +# Configuration update +dotnet-http PUT api.example.com/config \ + Authorization:"Bearer admin-token" \ + database[host]=localhost \ + database[port]:=5432 \ + database[ssl]:=true \ + cache[enabled]:=true \ + cache[ttl]:=3600 \ + features:='["auth", "logging", "monitoring"]' \ + limits[requests_per_minute]:=1000 \ + limits[max_file_size]:=10485760 +``` + +## Content-Type Handling + +### Automatic Content-Type + +```bash +# JSON (default for field syntax) +dotnet-http POST api.example.com/data name=John +# Content-Type: application/json + +# Form data +dotnet-http POST api.example.com/data --form name=John +# Content-Type: application/x-www-form-urlencoded + +# Multipart +dotnet-http POST api.example.com/data --multipart name=John file@data.txt +# Content-Type: multipart/form-data +``` + +### Manual Content-Type + +```bash +# Override content type +dotnet-http POST api.example.com/data \ + Content-Type:"application/vnd.api+json" \ + name=John age:=30 + +# XML content +dotnet-http POST api.example.com/data \ + Content-Type:"application/xml" \ + @data.xml +``` + +## Advanced Data Handling + +### Conditional Fields + +```bash +# Only include fields if they have values +dotnet-http POST api.example.com/users \ + name=John \ + email=john@example.com \ + $([ "$PHONE" ] && echo "phone=$PHONE") \ + $([ "$COMPANY" ] && echo "company=$COMPANY") +``` + +### Dynamic Values + +```bash +# Use command substitution +dotnet-http POST api.example.com/events \ + timestamp:=$(date +%s) \ + uuid="$(uuidgen)" \ + hostname="$(hostname)" +``` + +### Environment Variables + +```bash +# Reference environment variables +dotnet-http POST api.example.com/deploy \ + Authorization:"Bearer $API_TOKEN" \ + version="$BUILD_VERSION" \ + environment="$DEPLOY_ENV" +``` + +## Validation and Testing + +### Schema Validation + +```bash +# Validate response against schema +dotnet-http POST api.example.com/users \ + name=John \ + email=john@example.com \ + --schema user-schema.json +``` + +### Response Testing + +```bash +# Test specific fields in response +dotnet-http POST api.example.com/users name=John | jq '.id != null' + +# Combine with shell scripting +response=$(dotnet-http POST api.example.com/users name=John --body) +user_id=$(echo $response | jq -r '.id') +dotnet-http GET api.example.com/users/$user_id +``` + +## Best Practices + +1. **Use appropriate data types** - Numbers as `:=123`, booleans as `:=true` +2. **Quote complex values** - Especially JSON objects and arrays +3. **Be consistent with naming** - Use snake_case or camelCase consistently +4. **Validate data structure** - Use `--offline` to preview requests +5. **Use files for large data** - Avoid very long command lines +6. **Consider security** - Don't put sensitive data in command history +7. **Use environment variables** - For tokens and configuration +8. **Test incrementally** - Start simple and add complexity + +## Next Steps + +- Learn about [authentication methods](authentication.md) +- Explore [file execution](file-execution.md) for complex data scenarios +- Check out [variable substitution](variables.md) for dynamic values +- See [examples](examples/common-use-cases.md) for real-world usage patterns \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5820c73..30ec76e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -7,7 +7,8 @@ MIT https://github.com/WeihanLi/dotnet-httpie https://github.com/WeihanLi/dotnet-httpie/tree/main/docs/ReleaseNotes.md - true + true + true diff --git a/src/HTTPie/Implement/OutputFormatter.cs b/src/HTTPie/Implement/OutputFormatter.cs index 2efff84..8c32fca 100644 --- a/src/HTTPie/Implement/OutputFormatter.cs +++ b/src/HTTPie/Implement/OutputFormatter.cs @@ -51,7 +51,7 @@ public sealed class OutputFormatter(IServiceProvider serviceProvider, ILogger OutputVerboseOption = new("-v", "--verbose") { - Description = "output request/response, response headers and response body" + Description = "output all request/response info, including request/response headers,properties,body" }; private static readonly Option OutputPrintModeOption = new("-p", "--print") @@ -131,8 +131,9 @@ private static string GetCommonOutput(HttpContext httpContext, OutputFormat outp var responseModel = httpContext.Response; var hasValidResponse = (int)responseModel.StatusCode > 0; + // The HttpVersion in ResponseMessage is the version used for the request after negotiation var requestVersion = hasValidResponse - ? httpContext.Response.RequestHttpVersion ?? httpContext.Response.HttpVersion + ? httpContext.Response.HttpVersion : httpContext.Request.HttpVersion ?? new Version(2, 0) ; var prettyOption = requestModel.ParseResult.GetValue(PrettyOption); @@ -266,17 +267,17 @@ private static string GetRequestVersionAndStatus(HttpContext httpContext, Versio var uri = new Uri(requestModel.Url); return $""" - {requestModel.Method.Method.ToUpper()} {uri.PathAndQuery} HTTP/{requestVersion.NormalizeHttpVersion()} + {requestModel.Method.Method.ToUpper()} {uri.PathAndQuery} {requestVersion.NormalizeHttpVersion()} Host: {uri.Host}{(uri.IsDefaultPort ? "" : $":{uri.Port}")} Schema: {uri.Scheme} - Url: {requestModel.Url} + [Url]: {requestModel.Url} """; } private static string GetResponseVersionAndStatus(HttpResponseModel responseModel) { return - $"HTTP/{responseModel.HttpVersion.NormalizeHttpVersion()} {(int)responseModel.StatusCode} {responseModel.StatusCode}"; + $"{responseModel.HttpVersion.NormalizeHttpVersion()} {(int)responseModel.StatusCode} {responseModel.StatusCode}"; } private static string GetHeadersString(IDictionary headers) diff --git a/src/HTTPie/Implement/RequestExecutor.cs b/src/HTTPie/Implement/RequestExecutor.cs index 96944d9..ea7ddd3 100644 --- a/src/HTTPie/Implement/RequestExecutor.cs +++ b/src/HTTPie/Implement/RequestExecutor.cs @@ -2,12 +2,14 @@ // Licensed under the MIT license. using HTTPie.Abstractions; +using HTTPie.Middleware; using HTTPie.Models; using HTTPie.Utilities; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; using System.Diagnostics; using System.Net; +using System.Text; using WeihanLi.Common.Extensions; namespace HTTPie.Implement; @@ -50,9 +52,14 @@ ILogger logger Description = "Request duration, 10s/1m or 00:01:00 ..." }; + private static readonly Option StreamOption = new("--stream", "-S") + { + Description = "Stream response body output as it arrives (text responses only)" + }; + public Option[] SupportedOptions() { - return [TimeoutOption, IterationOption, DurationOption, VirtualUserOption]; + return [TimeoutOption, IterationOption, DurationOption, VirtualUserOption, StreamOption]; } public async ValueTask ExecuteAsync(HttpContext httpContext) @@ -60,6 +67,11 @@ public async ValueTask ExecuteAsync(HttpContext httpContext) var requestModel = httpContext.Request; await requestPipeline(requestModel); LogRequestModel(requestModel); + + // Set streaming mode flag early, before any early returns + var streamMode = requestModel.ParseResult.HasOption(StreamOption); + httpContext.UpdateFlag(Constants.FlagNames.IsStreamingMode, streamMode); + if (requestModel.ParseResult.HasOption(OutputFormatter.OfflineOption)) { RequestShouldBeOffline(); @@ -104,8 +116,17 @@ public async ValueTask ExecuteAsync(HttpContext httpContext) } else { - httpContext.Response = await InvokeRequest(client, httpContext, httpContext.RequestCancelled); - await responsePipeline(httpContext); + if (streamMode) + { + await InvokeStreamingRequest(client, httpContext, httpContext.RequestCancelled); + // Mark that streaming actually completed + httpContext.UpdateFlag(Constants.FlagNames.StreamingCompleted, true); + } + else + { + httpContext.Response = await InvokeRequest(client, httpContext, httpContext.RequestCancelled); + await responsePipeline(httpContext); + } } async Task InvokeLoadTest(HttpClient httpClient) @@ -187,6 +208,176 @@ private async Task InvokeRequest(HttpClient httpClient, HttpC return responseModel; } + private async Task InvokeStreamingRequest(HttpClient httpClient, HttpContext httpContext, + CancellationToken cancellationToken) + { + try + { + using var requestMessage = await requestMapper.ToRequestMessage(httpContext); + LogRequestMessage(requestMessage); + httpContext.Request.Timestamp = DateTimeOffset.Now; + var startTime = Stopwatch.GetTimestamp(); + + // Send request with HttpCompletionOption.ResponseHeadersRead to start streaming + using var responseMessage = await httpClient.SendAsync(requestMessage, + HttpCompletionOption.ResponseHeadersRead, cancellationToken); + + var elapsed = ProfilerHelper.GetElapsedTime(startTime); + LogResponseMessage(responseMessage); + + // Build response model with headers only + var responseModel = new HttpResponseModel + { + RequestHttpVersion = responseMessage.RequestMessage?.Version, + HttpVersion = responseMessage.Version, + StatusCode = responseMessage.StatusCode, + Headers = responseMessage.Headers + .Union(responseMessage.Content.Headers) + .ToDictionary(x => x.Key, x => new Microsoft.Extensions.Primitives.StringValues(x.Value.ToArray())), + Timestamp = httpContext.Request.Timestamp.Add(elapsed), + Elapsed = elapsed + }; + + httpContext.Response = responseModel; + + // Run response pipeline for headers (e.g., to set properties) + await responsePipeline(httpContext); + + // Check if we should stream based on content type + var isTextResponse = IsTextResponse(responseMessage); + var downloadMode = httpContext.Request.ParseResult.HasOption(DownloadMiddleware.DownloadOption); + + if (!isTextResponse || downloadMode) + { + // Fall back to buffered mode for binary content or downloads + responseModel.Bytes = await responseMessage.Content.ReadAsByteArrayAsync(cancellationToken); + if (isTextResponse) + { + try + { + responseModel.Body = responseModel.Bytes.GetString(); + } + catch (Exception ex) + { + // Unable to decode response as text, likely encoding issue + LogException(ex); + } + } + return; + } + + // Output headers immediately + var outputFormat = OutputFormatter.GetOutputFormat(httpContext); + + if (outputFormat.HasFlag(OutputFormat.ResponseHeaders) || outputFormat == OutputFormat.ResponseInfo) + { + var headerOutput = GetStreamingHeaderOutput(httpContext); + await Console.Out.WriteLineAsync(headerOutput); + } + + // Stream the body + if (outputFormat.HasFlag(OutputFormat.ResponseBody) || outputFormat == OutputFormat.ResponseInfo) + { + await using var stream = await responseMessage.Content.ReadAsStreamAsync(cancellationToken); + + // Get encoding from Content-Type header or default to UTF-8 + var encoding = responseMessage.Content.Headers.ContentType?.CharSet is { } charset + ? System.Text.Encoding.GetEncoding(charset) + : System.Text.Encoding.UTF8; + + using var reader = new StreamReader(stream, encoding); + + var bodyBuilder = new StringBuilder(); + string? line; + while ((line = await reader.ReadLineAsync(cancellationToken)) is not null) + { + await Console.Out.WriteLineAsync(line); + bodyBuilder.AppendLine(line); + } + + // Store the body for potential later use + responseModel.Body = bodyBuilder.ToString(); + responseModel.Bytes = encoding.GetBytes(responseModel.Body); + } + else + { + // Even if not outputting body, we need to consume it + responseModel.Bytes = await responseMessage.Content.ReadAsByteArrayAsync(cancellationToken); + responseModel.Body = responseModel.Bytes.GetString(); + } + + LogRequestDuration(httpContext.Request.Url, httpContext.Request.Method, responseModel.StatusCode, elapsed); + } + catch (OperationCanceledException operationCanceledException) when (cancellationToken.IsCancellationRequested) + { + LogRequestCancelled(operationCanceledException); + } + catch (Exception exception) + { + LogException(exception); + } + } + + private static bool IsTextResponse(HttpResponseMessage response) + { + // When ContentType is null, assume text response for compatibility + // This matches the behavior of ResponseMapper.IsTextResponse + if (response.Content.Headers.ContentType?.MediaType is null) + { + return true; + } + var contentType = response.Content.Headers.ContentType; + var mediaType = contentType.MediaType; + var isTextContent = mediaType.StartsWith("text/", StringComparison.OrdinalIgnoreCase) + || mediaType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase) + || mediaType.StartsWith("application/xml", StringComparison.OrdinalIgnoreCase) + || mediaType.StartsWith("application/javascript", StringComparison.OrdinalIgnoreCase) + ; + return isTextContent; + } + + private string GetStreamingHeaderOutput(HttpContext httpContext) + { + var responseModel = httpContext.Response; + var requestModel = httpContext.Request; + var outputFormat = OutputFormatter.GetOutputFormat(httpContext); + var output = new StringBuilder(); + + // Request headers if needed + if (outputFormat.HasFlag(OutputFormat.RequestHeaders)) + { + var requestVersion = responseModel.HttpVersion; + var uri = new Uri(requestModel.Url); + output.AppendLine($"{requestModel.Method.Method.ToUpper()} {uri.PathAndQuery} {requestVersion.NormalizeHttpVersion()}"); + output.AppendLine($"Host: {uri.Host}{(uri.IsDefaultPort ? "" : $":{uri.Port}")}"); + output.AppendLine($"Schema: {uri.Scheme}"); + output.AppendLine($"[Url]: {requestModel.Url}"); + output.AppendLine(string.Join(Environment.NewLine, + requestModel.Headers.Select(h => $"{h.Key}: {h.Value}").OrderBy(h => h))); + + if (outputFormat.HasFlag(OutputFormat.Properties) && requestModel.Properties.Count > 0) + { + output.AppendLine(string.Join(Environment.NewLine, + requestModel.Properties.Select(h => $"[{h.Key}]: {h.Value}").OrderBy(h => h))); + } + output.AppendLine(); + } + + // Response headers + output.AppendLine($"{responseModel.HttpVersion.NormalizeHttpVersion()} {(int)responseModel.StatusCode} {responseModel.StatusCode}"); + output.AppendLine(string.Join(Environment.NewLine, + responseModel.Headers.Select(h => $"{h.Key}: {h.Value}").OrderBy(h => h))); + + if (outputFormat.HasFlag(OutputFormat.Properties) && responseModel.Properties.Count > 0) + { + output.AppendLine(string.Join(Environment.NewLine, + responseModel.Properties.Select(h => $"[{h.Key}]: {h.Value}").OrderBy(h => h))); + } + + output.AppendLine(); + return output.ToString(); + } + [LoggerMessage(Level = LogLevel.Debug, EventId = 0, Message = "Request should be offline, wont send request")] private partial void RequestShouldBeOffline(); diff --git a/src/HTTPie/Middleware/RequestDataMiddleware.cs b/src/HTTPie/Middleware/RequestDataMiddleware.cs index 85a14d3..bee0eb5 100644 --- a/src/HTTPie/Middleware/RequestDataMiddleware.cs +++ b/src/HTTPie/Middleware/RequestDataMiddleware.cs @@ -165,7 +165,7 @@ private static void ParseNestedJsonItem(string item, JsonNode rootNode) if (rootArrayKey.Name == "root" || string.IsNullOrEmpty(rootArrayKey.Name)) { // Array append operation []= - rootArray.Add(CreateJsonValue(value, isRawValue)); + rootArray.Add((JsonNode?)CreateJsonValue(value, isRawValue)); } else { @@ -175,7 +175,7 @@ private static void ParseNestedJsonItem(string item, JsonNode rootNode) // Extend array if necessary while (rootArray.Count <= index) { - rootArray.Add(JsonValue.Create((string?)null)); + rootArray.Add((JsonNode?)null); } rootArray[index] = CreateJsonValue(value, isRawValue); } @@ -199,14 +199,14 @@ private static void ParseNestedJsonItem(string item, JsonNode rootNode) if (key.Name == "root" || string.IsNullOrEmpty(key.Name)) { // Array append operation - add new object to continue navigation - currentArray.Add(new JsonObject()); + currentArray.Add((JsonNode?)new JsonObject()); currentNode = currentArray[^1]!; } else if (int.TryParse(key.Name, out int index)) { while (currentArray.Count <= index) { - currentArray.Add(new JsonObject()); + currentArray.Add((JsonNode?)new JsonObject()); } currentNode = currentArray[index]!; } @@ -249,7 +249,7 @@ private static void ParseNestedJsonItem(string item, JsonNode rootNode) if (currentNode is JsonArray currentArray) { // Direct array access - currentArray.Add(CreateJsonValue(value, isRawValue)); + currentArray.Add((JsonNode?)CreateJsonValue(value, isRawValue)); } else { @@ -259,7 +259,7 @@ private static void ParseNestedJsonItem(string item, JsonNode rootNode) objectNode[finalKey.Name] = new JsonArray(); } var array = objectNode[finalKey.Name]!.AsArray(); - array.Add(CreateJsonValue(value, isRawValue)); + array.Add((JsonNode?)CreateJsonValue(value, isRawValue)); } } else diff --git a/src/HTTPie/Models/HttpResponseModel.cs b/src/HTTPie/Models/HttpResponseModel.cs index 06a84ff..1b39096 100644 --- a/src/HTTPie/Models/HttpResponseModel.cs +++ b/src/HTTPie/Models/HttpResponseModel.cs @@ -17,7 +17,7 @@ public sealed class HttpResponseModel [Newtonsoft.Json.JsonIgnore] [System.Text.Json.Serialization.JsonIgnore] - public byte[] Bytes { get; init; } = []; + public byte[] Bytes { get; set; } = []; public string Body { get; set; } = string.Empty; public DateTimeOffset Timestamp { get; set; } public TimeSpan Elapsed { get; set; } diff --git a/src/HTTPie/Utilities/Constants.cs b/src/HTTPie/Utilities/Constants.cs index 16c2cc7..7d91731 100644 --- a/src/HTTPie/Utilities/Constants.cs +++ b/src/HTTPie/Utilities/Constants.cs @@ -34,5 +34,7 @@ public static class FlagNames { public const string IsFormContentType = "IsFormContentType"; public const string IsLoadTest = "IsLoadTest"; + public const string IsStreamingMode = "IsStreamingMode"; + public const string StreamingCompleted = "StreamingCompleted"; } } diff --git a/src/HTTPie/Utilities/Helpers.cs b/src/HTTPie/Utilities/Helpers.cs index db24d15..d9b032b 100644 --- a/src/HTTPie/Utilities/Helpers.cs +++ b/src/HTTPie/Utilities/Helpers.cs @@ -241,13 +241,26 @@ private static async Task HttpCommandHandler(ParseResult parseResult, Cancellati await serviceProvider.GetRequiredService() .ParseAsync(requestModel); + // Check for streaming mode option early by checking tokens + // This is needed for tests that use an internal handler + var hasStreamOption = parseResult.Tokens.Any(t => + t.Value.Equals("--stream", StringComparison.OrdinalIgnoreCase) || + t.Value.Equals("-S", StringComparison.OrdinalIgnoreCase)); + context.UpdateFlag(Constants.FlagNames.IsStreamingMode, hasStreamOption); + if (internalHandler is null) { await serviceProvider.ResolveRequiredService() .ExecuteAsync(context); - var output = await serviceProvider.ResolveRequiredService() - .GetOutput(context); - await Console.Out.WriteLineAsync(output.Trim()); + + // Skip output formatting if streaming actually completed (output already written) + var streamingCompleted = context.GetFlag(Constants.FlagNames.StreamingCompleted); + if (!streamingCompleted) + { + var output = await serviceProvider.ResolveRequiredService() + .GetOutput(context); + await Console.Out.WriteLineAsync(output.Trim()); + } } else { @@ -263,8 +276,14 @@ public static bool HasOption(this ParseResult parseResult, Option option) public static string NormalizeHttpVersion(this Version version) { - var versionString = version.ToString(2); - return versionString.TrimEnd('0', '.'); + if (version.Major < 2) + { + // http/1.1 + return $"http/{version.Major}.{version.Minor}"; + } + + // h2, h3 + return $"h{version.Major}"; } public static HttpClientHandler GetHttpClientHandler() diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 2b4e85e..ef0cfc7 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -5,7 +5,7 @@ false true - true + true diff --git a/tests/HTTPie.UnitTest/Utilities/HelpersTest.cs b/tests/HTTPie.UnitTest/Utilities/HelpersTest.cs index a7367f4..10a9f6f 100644 --- a/tests/HTTPie.UnitTest/Utilities/HelpersTest.cs +++ b/tests/HTTPie.UnitTest/Utilities/HelpersTest.cs @@ -49,4 +49,30 @@ public void SplitTest(string commandLine) Assert.Equal("--raw", args[0]); Assert.Equal(@"{""Id"":1,""Name"":""Test""}", args[1]); } + + [Fact] + public async Task StreamingOption_IsRecognized() + { + var input = "GET reservation.weihanli.xyz/health --stream --offline"; + var services = new ServiceCollection() + .AddLogging() + .RegisterApplicationServices() + .BuildServiceProvider(); + await services.Handle(input, (_, _) => Task.CompletedTask); + var httpContext = services.GetRequiredService(); + Assert.True(httpContext.GetFlag(Constants.FlagNames.IsStreamingMode)); + } + + [Fact] + public async Task StreamingOption_NotSetByDefault() + { + var input = "GET reservation.weihanli.xyz/health --offline"; + var services = new ServiceCollection() + .AddLogging() + .RegisterApplicationServices() + .BuildServiceProvider(); + await services.Handle(input, (_, _) => Task.CompletedTask); + var httpContext = services.GetRequiredService(); + Assert.False(httpContext.GetFlag(Constants.FlagNames.IsStreamingMode)); + } }