refactor: split main.go into packages (internal/api, internal/fission, internal/runtime, cmd/server)
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
// Package runtime отвечает за подготовку исходного кода функции
|
||||
// к загрузке в Fission: упаковка в zip, обёртка ESM для Node.js и т.д.
|
||||
// Каждый язык — отдельный файл; общая логика zip — в helpers.go.
|
||||
package runtime
|
||||
@@ -0,0 +1,30 @@
|
||||
package runtime
|
||||
|
||||
// DefaultEntrypoint возвращает точку входа по умолчанию для языка lang.
|
||||
//
|
||||
// Формат entrypoint зависит от среды выполнения Fission:
|
||||
// - python: "module.function" → Fission импортирует модуль и вызывает функцию
|
||||
// - nodejs: "main" → имя экспортированной default-функции в main.js
|
||||
// - php: "main.php::handler" → имя файла + "::" + имя функции
|
||||
// - ruby: "handler" → имя метода в загруженном файле
|
||||
// - perl: "handler" → имя функции в загруженном файле
|
||||
// - go: "Handler" → экспортированная Go-функция (с большой буквы)
|
||||
func DefaultEntrypoint(lang string) string {
|
||||
switch lang {
|
||||
case "nodejs":
|
||||
return "main"
|
||||
case "php":
|
||||
// Fission php-env: filename::functionName
|
||||
return "main.php::handler"
|
||||
case "ruby":
|
||||
return "handler"
|
||||
case "perl":
|
||||
return "handler"
|
||||
case "go":
|
||||
// Go: экспортированная функция (заглавная) — go/plugin требует экспорт
|
||||
return "Handler"
|
||||
default:
|
||||
// python и неизвестные языки: "main.main" = модуль main, функция main
|
||||
return "main.main"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// BuildGoSourceZip упаковывает исходный Go-код в zip для Fission source package.
|
||||
//
|
||||
// Fission Go-среда работает в два этапа:
|
||||
// 1. Package создаётся с source.literal = zip(handler.go + go.mod)
|
||||
// 2. go-builder job компилирует его в плагин (.so) — это занимает ~40s первый раз
|
||||
//
|
||||
// Важно: source package, а НЕ deployment package — Fission сам компилирует.
|
||||
// Entrypoint по умолчанию "Handler" (экспортированная функция в handler.go).
|
||||
func BuildGoSourceZip(code string) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
zw := zip.NewWriter(&buf)
|
||||
|
||||
// handler.go — пользовательский код
|
||||
fw, err := zw.Create("handler.go")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := fw.Write([]byte(code)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// go.mod — минимальный модуль для сборки; go-builder добавит нужные зависимости
|
||||
goMod := "module github.com/user/fn\n\ngo 1.23\n"
|
||||
fw2, err := zw.Create("go.mod")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := fw2.Write([]byte(goMod)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := zw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// buildZip создаёт zip-архив с одним файлом fileName и содержимым content.
|
||||
// Вспомогательная функция: используется в buildScriptZip (PHP/Ruby/Perl),
|
||||
// а также как основа для buildGoSourceZip и buildJSDeployZip.
|
||||
func buildZip(fileName string, content []byte) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
zw := zip.NewWriter(&buf)
|
||||
|
||||
fw, err := zw.Create(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := fw.Write(content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := zw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// BuildJSDeployZip упаковывает Node.js код в zip для Fission deployment package.
|
||||
//
|
||||
// Почему zip, а не просто literal-код:
|
||||
// Fission node-env v3 поддерживает ESM модули. Чтобы Node.js трактовал файл
|
||||
// как ESM, нужен package.json с {"type":"module"} рядом с main.js.
|
||||
// Без него `import` синтаксис вызовет "require is not defined" или SyntaxError.
|
||||
//
|
||||
// Почему new Function:
|
||||
// Пользовательский код пишет CJS-стиль (module.exports = ...) но мы хотим ESM wrapper.
|
||||
// new Function() изолирует module/exports от глобального ESM контекста и позволяет
|
||||
// исполнять CJS-код без изменений.
|
||||
//
|
||||
// Результат: zip с двумя файлами:
|
||||
// - package.json: {"type":"module"}
|
||||
// - main.js: ESM wrapper + инлайн пользовательский код через new Function
|
||||
func BuildJSDeployZip(code string) ([]byte, error) {
|
||||
// Сериализуем пользовательский код в JSON строку чтобы безопасно инлайнить
|
||||
// в JavaScript-литерал — экранирует кавычки, переводы строк, спецсимволы.
|
||||
codeJSON, err := json.Marshal(code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal user code: %w", err)
|
||||
}
|
||||
|
||||
// main.js — ESM wrapper:
|
||||
// 1. new Function создаёт функцию в пустом модульном контексте (нет import/export)
|
||||
// 2. Передаём ей module и exports как параметры → пользовательский CJS код работает
|
||||
// 3. Экспортируем default async function для Fission node-env entrypoint
|
||||
wrapper := fmt.Sprintf(`const __mod = { exports: {} };
|
||||
(new Function('module', 'exports', %s))(__mod, __mod.exports);
|
||||
const _fn = __mod.exports;
|
||||
|
||||
export default async function(ctx) {
|
||||
const fn = typeof _fn === 'function' ? _fn : (_fn.default || _fn.handler || _fn.main);
|
||||
if (!fn) throw new Error('no exported function found in user code');
|
||||
const result = await fn(ctx);
|
||||
if (!result) return { status: 200, body: '' };
|
||||
if (typeof result.status !== 'undefined') return result;
|
||||
return { status: 200, ...result };
|
||||
}
|
||||
`, string(codeJSON))
|
||||
|
||||
var buf bytes.Buffer
|
||||
zw := zip.NewWriter(&buf)
|
||||
|
||||
// package.json: включает ESM режим для всего архива
|
||||
pkgfw, err := zw.Create("package.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := pkgfw.Write([]byte(`{"type":"module"}`)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fw, err := zw.Create("main.js")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := fw.Write([]byte(wrapper)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := zw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package runtime
|
||||
|
||||
// BuildScriptZip создаёт zip-архив с одним файлом fileName и содержимым code.
|
||||
// Используется для PHP, Ruby, Perl — языков где среда Fission ожидает
|
||||
// именованный файл (handler.rb, handler.pl, main.php и т.д.) внутри архива.
|
||||
//
|
||||
// Почему zip, а не просто literal:
|
||||
// Fission poolmgr при специализации пода распаковывает deployment archive,
|
||||
// находит нужный файл по имени и загружает его в среду выполнения.
|
||||
// Если передать просто байты кода в deployment.literal — среда не знает расширение.
|
||||
func BuildScriptZip(code, fileName string) ([]byte, error) {
|
||||
return buildZip(fileName, []byte(code))
|
||||
}
|
||||
Reference in New Issue
Block a user