eggjs / egg

🥚 Born to build better enterprise frameworks and apps with Node.js & Koa
https://eggjs.org
MIT License
18.88k stars 1.81k forks source link

[Feature Request] 有egg基于ServerLess部署的指导方案吗? #3352

Closed ZQun closed 5 years ago

ZQun commented 5 years ago

Background

可以用egg做阿里云的函数计算(ServerLess)开发吗? 看到函数计算文档,python使用Django框架开发的示例

使用egg可以实现ServerLess开发吗??

Proposal

可以参照Django案例的项目目录结构来写egg函数程序吗?求实现方案~

exports.handler = function (request, response, context) {
    console.log('hello world');
    callback(null, 'hello world');
};

Additional context

ZQun commented 5 years ago

我看见函数计算(ServerLess)中需要编写函数入口,如果在egg中实现,是不是也需要提供入口文件~😜

okoala commented 5 years ago

https://github.com/eggjs/egg/issues/3180 目前 @popomore 在搞 serverless 这块

thonatos commented 5 years ago

之前基于compose做了一个非常简易的版本。 大概率要烂尾了,拿出来给有需要或者喜欢自己折腾的的同学参考吧~

入口

// index.js
'use strict';

const App = require('./app');

module.exports.handler = async (request, response, _ctx) => {
  const app = new App();

  app.use(async (ctx, next) => {
    await next();
    console.log('it\'s fucking beautiful!');
  });

  // router handler

  // 传入函数计算默认参数;
  return app.run(request, response, _ctx);
};

应用

// app.js
const delegate = require('delegates');
const compose = require('koa-compose');
const config = require('./conf');
const onerror = require('./lib/onerror');
const respond = require('./lib/respond');
const middleware = require('./middleware');

const urllib = require('urllib');
const nunjucks = require('nunjucks');

module.exports = class App {
  constructor(options) {
    this.curl = urllib;
    this.config = Object.assign(config, options);
    this.renderString = nunjucks.renderString.bind(nunjucks);
    this.middleware = Object.values(middleware).map(fn => fn(this));
  }

  createContext(request, response, _ctx) {
    this.env = _ctx;

    const context = {};
    context.app = request.app = response.app = this;
    context.req = context.request = request.req = response.req = request;
    context.res = context.response = request.res = response.res = response;

    delegate(context, 'app')
      .access('env')
      .access('curl')
      .access('config')
      .method('renderString');

    delegate(context, 'request')
      .access('url')
      .access('path')
      .access('method')
      .access('queries')
      .access('headers')
      .access('clientIP');

    delegate(context, 'response')
      .method('send')
      .method('setHeader')
      .method('deleteHeader')
      .method('setStatusCode');

    return context;
  }

  use(fn) {
    this.middleware.push(fn);
  }

  run(request, response, _ctx) {
    const ctx = this.createContext(request, response, _ctx);

    const handleError = err => onerror(err, ctx);
    const handleResponse = () => respond(ctx);
    const fn = compose(this.middleware);

    return fn(ctx)
      .then(handleResponse)
      .catch(handleError);
  }
};

响应处理

// lib/respond.js

const url = require('url');
const path = require('path');
const mime = require('mime-types');
const status = require('statuses');
const patchMatch = require('../utils/pathMatch');

module.exports = async ctx => {
  const { pack } = ctx;
  console.log('pack', pack);

  const { packName, pathName } = pack;

  // not match pack
  if (!packName) {
    console.log('not match pack');
    ctx.setHeader('Content-Type', mime.contentType('index.html'));
    ctx.setStatusCode(403);
    ctx.send(status[403]);
    return;
  }

  const prefix = `${ctx.config.oss.prefix}/${packName}`;
  const assetsMap = await ctx.getObject(`${prefix}/config.json`);
  const matched = patchMatch(pathName, assetsMap.pages);

  console.log('matched', matched);

  if (!matched) {
    console.log('not found.');
    ctx.setHeader('Content-Type', mime.contentType('index.html'));
    ctx.setStatusCode(404);
    ctx.send(status[404]);
    return;
  }

  const page = assetsMap.pages[matched.route];
  const { template, data, proxy } = page;
  console.log('page', template, data, proxy);

  // html
  if (template) {
    let tpl, locals;

    const templatePath = url.parse(template).pathname;
    const templateName = path.basename(templatePath);
    tpl = await ctx.getObject(templatePath);

    // locals
    const env = {
      params: matched.params || {},
      queries: ctx.queries,
    };
    console.log('env', env);

    if (data) {
      const dataPath = url.parse(data).pathname;
      locals = await ctx.getObject(dataPath);
      console.log('locals', locals);
    }

    const body = ctx.renderString(tpl, Object.assign({}, env, locals || {}));
    // console.log(body);
    ctx.setHeader('Content-Type', mime.contentType(templateName));
    ctx.setHeader('Cache-Control', 'max-age=0, s-maxage=120, must-revalidate');
    ctx.send(body);
    return;
  }

  // assets
  if (proxy) {
    const proxyName = path.basename(proxy);
    const { data: asset } = await ctx.curl.request(proxy);
    ctx.setHeader('Content-Type', mime.contentType(proxyName));
    ctx.setHeader('Cache-Control', 'max-age=0, s-maxage=120, must-revalidate');
    ctx.send(asset.toString());
    return;
  }
};

错误处理

// lib/onerror.js
'use strict';

module.exports = (err, ctx) => {
  const msg = err.stack || err.toString();
  console.error();
  console.error(msg.replace(/^/gm, '  '));
  console.error();

  ctx.setStatusCode(500);
  ctx.send(msg);
};

中间件

// middleware/oss.js

const mime = require('mime-types');
const oss = require('ali-oss');

module.exports = () => {
  return async function storage(ctx, next) {    
    const { region, bucket, internal } = ctx.config.oss;
    const { accessKeyId, accessKeySecret, securityToken } = ctx.env.credentials;

    // init oss
    const client = oss({
      region,
      internal,
      accessKeyId,
      accessKeySecret,
      stsToken: securityToken,
    });
    client.useBucket(bucket);
    ctx.client = client;

    // make curl
    ctx.getObject = async url => {
      const mimetype = mime.lookup(url);
      if (!mimetype) {
        return;
      }
      const raw = await client.get(url);
      if (mimetype === 'application/json') {
        return JSON.parse(raw.content.toString());
      }
      return raw.content.toString();
    };

    await next();
  };
};
popomore commented 5 years ago

fc 最好是能自己写 Runtime,这样的话把 egg 集成到 runtime 里面,用户代码不需要感知,现在也正在实践没有直接能用的产出。

hugohua commented 5 years ago

这个需求还有人在跟进吗?

ZQun commented 5 years ago

@hugohua 关注下midway-faas

yugasun commented 4 years ago

这有个基于 serverless framework 的方案:https://github.com/serverless/components/tree/master/templates/tencent-eggjs