mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-18 08:29:55 +01:00
## Summary
I've successfully implemented an HTTP API for Wails v3 that provides a workaround for the CORS issue. Here's what was created: ### Backend Implementation: 1. **`http.go`** - Core HTTP functionality with: - Support for GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS methods - Request/response type definitions - Error handling and timeout support - Automatic User-Agent header 2. **`messageprocessor_http.go`** - Message processor for handling HTTP requests from frontend 3. **Updated `messageprocessor.go`** - Added HTTP request constant and routing ### Frontend Implementation: 1. **`http.js`** - JavaScript runtime API with: - `Fetch()` - Generic method for full control - Convenience methods: `Get()`, `Post()`, `Put()`, `Delete()`, `Patch()`, `Head()` - Automatic JSON serialization for POST/PUT/PATCH bodies - Promise-based API 2. **Updated runtime files**: - Added HTTP to object names in `runtime.js` - Added support for POST body in runtime calls - Exported HTTP module in `main.js` and `api/index.js` ### Example Application: Created a complete example in `v3/examples/http-cors-workaround/` demonstrating: - GET/POST requests - Custom headers - Error handling - Timeout functionality ### How It Works: Instead of making requests directly from the frontend (which fails due to CORS), the HTTP API: 1. Sends the request details to the Go backend 2. Go makes the actual HTTP request (no CORS restrictions) 3. Returns the response to the frontend This completely bypasses CORS issues while maintaining a familiar API similar to fetch/axios.
This commit is contained in:
parent
f28c9515ad
commit
e510f8d897
11 changed files with 641 additions and 4 deletions
77
v3/examples/http-cors-workaround/README.md
Normal file
77
v3/examples/http-cors-workaround/README.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# HTTP CORS Workaround Example
|
||||
|
||||
This example demonstrates how to use the Wails v3 HTTP runtime API to bypass CORS issues when making network requests from the frontend.
|
||||
|
||||
## The Problem
|
||||
|
||||
When using Wails with the `wails://wails` protocol on Linux/macOS, CORS restrictions prevent direct HTTP requests from the frontend to external APIs. This is because:
|
||||
|
||||
1. Wails uses a custom protocol (`wails://wails`) which is not recognized by external servers
|
||||
2. The browser sends an empty Origin header for custom protocols
|
||||
3. External servers reject requests without a valid Origin header
|
||||
|
||||
## The Solution
|
||||
|
||||
The Wails HTTP runtime API allows you to make HTTP requests through the Go backend, completely bypassing CORS restrictions.
|
||||
|
||||
## Usage
|
||||
|
||||
Instead of using `fetch` or `axios` directly:
|
||||
|
||||
```javascript
|
||||
// This will fail with CORS error
|
||||
const response = await fetch('https://api.example.com/data');
|
||||
```
|
||||
|
||||
Use the Wails HTTP API:
|
||||
|
||||
```javascript
|
||||
// This works - request is made from Go backend
|
||||
const response = await wails.HTTP.Get('https://api.example.com/data');
|
||||
```
|
||||
|
||||
## API Methods
|
||||
|
||||
- `wails.HTTP.Get(url, options)` - GET request
|
||||
- `wails.HTTP.Post(url, body, options)` - POST request
|
||||
- `wails.HTTP.Put(url, body, options)` - PUT request
|
||||
- `wails.HTTP.Delete(url, options)` - DELETE request
|
||||
- `wails.HTTP.Patch(url, body, options)` - PATCH request
|
||||
- `wails.HTTP.Head(url, options)` - HEAD request
|
||||
- `wails.HTTP.Fetch(options)` - Generic request with full options
|
||||
|
||||
## Example with Headers and Timeout
|
||||
|
||||
```javascript
|
||||
const response = await wails.HTTP.Post('https://api.example.com/users', {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com'
|
||||
}, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer token123',
|
||||
'X-Custom-Header': 'value'
|
||||
},
|
||||
timeout: 10 // 10 seconds timeout
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
console.error('Request failed:', response.error);
|
||||
} else {
|
||||
console.log('Status:', response.statusCode);
|
||||
console.log('Data:', JSON.parse(response.body));
|
||||
}
|
||||
```
|
||||
|
||||
## Running the Example
|
||||
|
||||
1. Navigate to this directory
|
||||
2. Run `wails3 dev`
|
||||
3. Click the buttons to test various HTTP methods
|
||||
4. Check the console for responses
|
||||
|
||||
## Notes
|
||||
|
||||
- All requests are made from the Go backend, so there are no CORS restrictions
|
||||
- The response includes `statusCode`, `headers`, `body`, and `error` (if any)
|
||||
- Request bodies are automatically JSON stringified for objects
|
||||
- Default timeout is 30 seconds, but can be customized
|
||||
204
v3/examples/http-cors-workaround/assets/index.html
Normal file
204
v3/examples/http-cors-workaround/assets/index.html
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>HTTP CORS Workaround Example</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
}
|
||||
.section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
pre {
|
||||
background-color: #f4f4f4;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.error {
|
||||
color: #dc3545;
|
||||
}
|
||||
.success {
|
||||
color: #28a745;
|
||||
}
|
||||
#loading {
|
||||
display: none;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>HTTP CORS Workaround Example</h1>
|
||||
|
||||
<div class="section">
|
||||
<h2>Test HTTP Requests</h2>
|
||||
<p>Click the buttons below to test various HTTP methods using the Wails HTTP API:</p>
|
||||
|
||||
<button onclick="testGet()">Test GET Request</button>
|
||||
<button onclick="testPost()">Test POST Request</button>
|
||||
<button onclick="testWithHeaders()">Test with Custom Headers</button>
|
||||
<button onclick="testError()">Test Error Handling</button>
|
||||
<button onclick="testTimeout()">Test Timeout</button>
|
||||
|
||||
<div id="loading">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Response:</h2>
|
||||
<pre id="response">Click a button to make a request</pre>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Wait for Wails to be ready
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('Wails HTTP API Example Ready');
|
||||
});
|
||||
|
||||
function showLoading(show) {
|
||||
document.getElementById('loading').style.display = show ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function showResponse(data, isError = false) {
|
||||
const responseEl = document.getElementById('response');
|
||||
responseEl.textContent = JSON.stringify(data, null, 2);
|
||||
responseEl.className = isError ? 'error' : 'success';
|
||||
}
|
||||
|
||||
async function testGet() {
|
||||
showLoading(true);
|
||||
try {
|
||||
const response = await wails.HTTP.Get('https://jsonplaceholder.typicode.com/posts/1');
|
||||
if (response.error) {
|
||||
showResponse({ error: response.error }, true);
|
||||
} else {
|
||||
showResponse({
|
||||
statusCode: response.statusCode,
|
||||
data: JSON.parse(response.body)
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
showResponse({ error: error.message }, true);
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function testPost() {
|
||||
showLoading(true);
|
||||
try {
|
||||
const postData = {
|
||||
title: 'Test Post from Wails',
|
||||
body: 'This post was made using the Wails HTTP API',
|
||||
userId: 1
|
||||
};
|
||||
|
||||
const response = await wails.HTTP.Post('https://jsonplaceholder.typicode.com/posts', postData);
|
||||
if (response.error) {
|
||||
showResponse({ error: response.error }, true);
|
||||
} else {
|
||||
showResponse({
|
||||
statusCode: response.statusCode,
|
||||
data: JSON.parse(response.body)
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
showResponse({ error: error.message }, true);
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function testWithHeaders() {
|
||||
showLoading(true);
|
||||
try {
|
||||
const response = await wails.HTTP.Fetch({
|
||||
url: 'https://httpbin.org/headers',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Custom-Header': 'Wails-HTTP-API',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
showResponse({ error: response.error }, true);
|
||||
} else {
|
||||
showResponse({
|
||||
statusCode: response.statusCode,
|
||||
data: JSON.parse(response.body)
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
showResponse({ error: error.message }, true);
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function testError() {
|
||||
showLoading(true);
|
||||
try {
|
||||
const response = await wails.HTTP.Get('https://httpstat.us/500');
|
||||
showResponse({
|
||||
statusCode: response.statusCode,
|
||||
body: response.body,
|
||||
error: response.error
|
||||
}, response.statusCode >= 400);
|
||||
} catch (error) {
|
||||
showResponse({ error: error.message }, true);
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function testTimeout() {
|
||||
showLoading(true);
|
||||
try {
|
||||
const response = await wails.HTTP.Fetch({
|
||||
url: 'https://httpstat.us/200?sleep=5000',
|
||||
method: 'GET',
|
||||
timeout: 2 // 2 second timeout
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
showResponse({ error: response.error }, true);
|
||||
} else {
|
||||
showResponse({
|
||||
statusCode: response.statusCode,
|
||||
body: response.body
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
showResponse({ error: error.message }, true);
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
33
v3/examples/http-cors-workaround/main.go
Normal file
33
v3/examples/http-cors-workaround/main.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"log"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed assets
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "HTTP CORS Workaround Example",
|
||||
Description: "Demonstrates using Wails HTTP API to bypass CORS",
|
||||
Assets: application.AssetOptions{
|
||||
FS: assets,
|
||||
},
|
||||
})
|
||||
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "HTTP CORS Workaround",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
URL: "/",
|
||||
})
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
1
v3/internal/runtime/desktop/api/http.js
Normal file
1
v3/internal/runtime/desktop/api/http.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from '../http';
|
||||
|
|
@ -15,8 +15,9 @@ import * as Screens from "./screens";
|
|||
import * as Dialogs from "./dialogs";
|
||||
import * as Events from "./events";
|
||||
import * as Window from "./window";
|
||||
import * as HTTP from "./http";
|
||||
|
||||
export { Clipboard, Application, Screens, Dialogs, Events, Window };
|
||||
export { Clipboard, Application, Screens, Dialogs, Events, Window, HTTP };
|
||||
|
||||
/**
|
||||
* Call a plugin method
|
||||
|
|
|
|||
134
v3/internal/runtime/desktop/http.js
Normal file
134
v3/internal/runtime/desktop/http.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
/* jshint esversion: 9 */
|
||||
|
||||
import {newRuntimeCallerWithID, objectNames} from "./runtime";
|
||||
|
||||
let call = newRuntimeCallerWithID(objectNames.HTTP);
|
||||
|
||||
let HTTPFetch = 0;
|
||||
|
||||
/**
|
||||
* Perform an HTTP request
|
||||
* @param {Object} options - The request options
|
||||
* @param {string} options.url - The URL to request
|
||||
* @param {string} [options.method='GET'] - The HTTP method
|
||||
* @param {Object} [options.headers] - Request headers
|
||||
* @param {string} [options.body] - Request body
|
||||
* @param {number} [options.timeout] - Request timeout in seconds
|
||||
* @returns {Promise<Object>} The response object
|
||||
*/
|
||||
export function Fetch(options) {
|
||||
// Ensure we have required fields
|
||||
if (!options || !options.url) {
|
||||
return Promise.reject(new Error("URL is required"));
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
const request = {
|
||||
url: options.url,
|
||||
method: options.method || 'GET',
|
||||
headers: options.headers || {},
|
||||
body: options.body || '',
|
||||
timeout: options.timeout || 30
|
||||
};
|
||||
|
||||
// For POST requests, we need to send the body differently
|
||||
if (request.body) {
|
||||
return call(HTTPFetch, null, JSON.stringify(request));
|
||||
}
|
||||
|
||||
return call(HTTPFetch, null, JSON.stringify(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for GET requests
|
||||
* @param {string} url - The URL to request
|
||||
* @param {Object} [options] - Additional options
|
||||
* @returns {Promise<Object>} The response object
|
||||
*/
|
||||
export function Get(url, options = {}) {
|
||||
return Fetch({ ...options, url, method: 'GET' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for POST requests
|
||||
* @param {string} url - The URL to request
|
||||
* @param {string|Object} body - The request body
|
||||
* @param {Object} [options] - Additional options
|
||||
* @returns {Promise<Object>} The response object
|
||||
*/
|
||||
export function Post(url, body, options = {}) {
|
||||
if (typeof body === 'object' && !(body instanceof String)) {
|
||||
body = JSON.stringify(body);
|
||||
options.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...(options.headers || {})
|
||||
};
|
||||
}
|
||||
return Fetch({ ...options, url, method: 'POST', body });
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for PUT requests
|
||||
* @param {string} url - The URL to request
|
||||
* @param {string|Object} body - The request body
|
||||
* @param {Object} [options] - Additional options
|
||||
* @returns {Promise<Object>} The response object
|
||||
*/
|
||||
export function Put(url, body, options = {}) {
|
||||
if (typeof body === 'object' && !(body instanceof String)) {
|
||||
body = JSON.stringify(body);
|
||||
options.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...(options.headers || {})
|
||||
};
|
||||
}
|
||||
return Fetch({ ...options, url, method: 'PUT', body });
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for DELETE requests
|
||||
* @param {string} url - The URL to request
|
||||
* @param {Object} [options] - Additional options
|
||||
* @returns {Promise<Object>} The response object
|
||||
*/
|
||||
export function Delete(url, options = {}) {
|
||||
return Fetch({ ...options, url, method: 'DELETE' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for PATCH requests
|
||||
* @param {string} url - The URL to request
|
||||
* @param {string|Object} body - The request body
|
||||
* @param {Object} [options] - Additional options
|
||||
* @returns {Promise<Object>} The response object
|
||||
*/
|
||||
export function Patch(url, body, options = {}) {
|
||||
if (typeof body === 'object' && !(body instanceof String)) {
|
||||
body = JSON.stringify(body);
|
||||
options.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...(options.headers || {})
|
||||
};
|
||||
}
|
||||
return Fetch({ ...options, url, method: 'PATCH', body });
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for HEAD requests
|
||||
* @param {string} url - The URL to request
|
||||
* @param {Object} [options] - Additional options
|
||||
* @returns {Promise<Object>} The response object
|
||||
*/
|
||||
export function Head(url, options = {}) {
|
||||
return Fetch({ ...options, url, method: 'HEAD' });
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import * as Application from './application';
|
|||
import * as Screens from './screens';
|
||||
import * as System from './system';
|
||||
import * as Browser from './browser';
|
||||
import * as HTTP from './http';
|
||||
import {Plugin, Call, callErrorCallback, callCallback, CallByID, CallByName} from "./calls";
|
||||
import {clientId} from './runtime';
|
||||
import {newWindow} from "./window";
|
||||
|
|
@ -61,6 +62,7 @@ export function newRuntime(windowName) {
|
|||
System,
|
||||
Screens,
|
||||
Browser,
|
||||
HTTP,
|
||||
Call,
|
||||
CallByID,
|
||||
CallByName,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export const objectNames = {
|
|||
Screens: 7,
|
||||
System: 8,
|
||||
Browser: 9,
|
||||
HTTP: 10,
|
||||
}
|
||||
export let clientId = nanoid();
|
||||
|
||||
|
|
@ -67,7 +68,7 @@ export function newRuntimeCaller(object, windowName) {
|
|||
};
|
||||
}
|
||||
|
||||
function runtimeCallWithID(objectID, method, windowName, args) {
|
||||
function runtimeCallWithID(objectID, method, windowName, args, body) {
|
||||
let url = new URL(runtimeURL);
|
||||
url.searchParams.append("object", objectID);
|
||||
url.searchParams.append("method", method);
|
||||
|
|
@ -80,6 +81,11 @@ function runtimeCallWithID(objectID, method, windowName, args) {
|
|||
if (args) {
|
||||
url.searchParams.append("args", JSON.stringify(args));
|
||||
}
|
||||
if (body) {
|
||||
fetchOptions.method = "POST";
|
||||
fetchOptions.body = body;
|
||||
fetchOptions.headers["Content-Type"] = "application/json";
|
||||
}
|
||||
fetchOptions.headers["x-wails-client-id"] = clientId;
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(url, fetchOptions)
|
||||
|
|
@ -100,7 +106,7 @@ function runtimeCallWithID(objectID, method, windowName, args) {
|
|||
}
|
||||
|
||||
export function newRuntimeCallerWithID(object, windowName) {
|
||||
return function (method, args=null) {
|
||||
return runtimeCallWithID(object, method, windowName, args);
|
||||
return function (method, args=null, body=null) {
|
||||
return runtimeCallWithID(object, method, windowName, args, body);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
126
v3/pkg/application/http.go
Normal file
126
v3/pkg/application/http.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package application
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPRequest represents an HTTP request from the frontend
|
||||
type HTTPRequest struct {
|
||||
Method string `json:"method"`
|
||||
URL string `json:"url"`
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Timeout int `json:"timeout,omitempty"` // timeout in seconds
|
||||
}
|
||||
|
||||
// HTTPResponse represents an HTTP response to send back to the frontend
|
||||
type HTTPResponse struct {
|
||||
StatusCode int `json:"statusCode"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Body string `json:"body"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// PerformHTTPRequest performs an HTTP request on behalf of the frontend
|
||||
func PerformHTTPRequest(request HTTPRequest) HTTPResponse {
|
||||
// Validate method
|
||||
method := strings.ToUpper(request.Method)
|
||||
if method != "GET" && method != "POST" && method != "PUT" && method != "DELETE" && method != "PATCH" && method != "HEAD" && method != "OPTIONS" {
|
||||
return HTTPResponse{
|
||||
StatusCode: 0,
|
||||
Error: fmt.Sprintf("Invalid HTTP method: %s", request.Method),
|
||||
}
|
||||
}
|
||||
|
||||
// Create HTTP client with timeout
|
||||
timeout := 30 * time.Second
|
||||
if request.Timeout > 0 {
|
||||
timeout = time.Duration(request.Timeout) * time.Second
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
// Create request
|
||||
var body io.Reader
|
||||
if request.Body != "" {
|
||||
body = bytes.NewBufferString(request.Body)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), method, request.URL, body)
|
||||
if err != nil {
|
||||
return HTTPResponse{
|
||||
StatusCode: 0,
|
||||
Error: fmt.Sprintf("Failed to create request: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
// Set headers
|
||||
for key, value := range request.Headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
// Set default User-Agent if not provided
|
||||
if req.Header.Get("User-Agent") == "" {
|
||||
req.Header.Set("User-Agent", "Wails/3.0")
|
||||
}
|
||||
|
||||
// Perform the request
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return HTTPResponse{
|
||||
StatusCode: 0,
|
||||
Error: fmt.Sprintf("Request failed: %v", err),
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read response body
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return HTTPResponse{
|
||||
StatusCode: resp.StatusCode,
|
||||
Error: fmt.Sprintf("Failed to read response body: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
// Build response headers
|
||||
responseHeaders := make(map[string]string)
|
||||
for key, values := range resp.Header {
|
||||
if len(values) > 0 {
|
||||
responseHeaders[key] = values[0]
|
||||
}
|
||||
}
|
||||
|
||||
return HTTPResponse{
|
||||
StatusCode: resp.StatusCode,
|
||||
Headers: responseHeaders,
|
||||
Body: string(bodyBytes),
|
||||
}
|
||||
}
|
||||
|
||||
// parseHTTPRequest parses the HTTP request from JSON
|
||||
func parseHTTPRequest(data string) (*HTTPRequest, error) {
|
||||
var request HTTPRequest
|
||||
err := json.Unmarshal([]byte(data), &request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse HTTP request: %v", err)
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if request.URL == "" {
|
||||
return nil, fmt.Errorf("URL is required")
|
||||
}
|
||||
if request.Method == "" {
|
||||
request.Method = "GET"
|
||||
}
|
||||
|
||||
return &request, nil
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ const (
|
|||
screensRequest = 7
|
||||
systemRequest = 8
|
||||
browserRequest = 9
|
||||
httpRequest = 10
|
||||
)
|
||||
|
||||
type MessageProcessor struct {
|
||||
|
|
@ -142,6 +143,8 @@ func (m *MessageProcessor) HandleRuntimeCallWithIDs(rw http.ResponseWriter, r *h
|
|||
m.processSystemMethod(method, rw, r, targetWindow, params)
|
||||
case browserRequest:
|
||||
m.processBrowserMethod(method, rw, r, targetWindow, params)
|
||||
case httpRequest:
|
||||
m.processHTTPMethod(method, rw, r, targetWindow, params)
|
||||
default:
|
||||
m.httpError(rw, "Unknown runtime call: %d", object)
|
||||
}
|
||||
|
|
|
|||
50
v3/pkg/application/messageprocessor_http.go
Normal file
50
v3/pkg/application/messageprocessor_http.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package application
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
httpFetch = 0
|
||||
)
|
||||
|
||||
func (m *MessageProcessor) processHTTPMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) {
|
||||
switch method {
|
||||
case httpFetch:
|
||||
m.httpFetch(rw, r, window)
|
||||
default:
|
||||
m.httpError(rw, "Unknown HTTP method: %d", method)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MessageProcessor) httpFetch(rw http.ResponseWriter, r *http.Request, window Window) {
|
||||
// Read the request body
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
m.httpError(rw, "Failed to read request body: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the HTTP request
|
||||
request, err := parseHTTPRequest(string(body))
|
||||
if err != nil {
|
||||
m.httpError(rw, "Invalid HTTP request: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Perform the HTTP request
|
||||
response := PerformHTTPRequest(*request)
|
||||
|
||||
// Marshal response to JSON
|
||||
responseJSON, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
m.httpError(rw, "Failed to marshal response: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send response
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Write(responseJSON)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue