Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
ZQun opened this issue Dec 29, 2018 · 7 comments
Closed

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

ZQun opened this issue Dec 29, 2018 · 7 comments
Assignees
Labels

Comments

@ZQun
Copy link

ZQun commented Dec 29, 2018

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
Copy link
Author

ZQun commented Dec 29, 2018

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

@okoala
Copy link
Member

okoala commented Jan 2, 2019

#3180
目前 @popomore 在搞 serverless 这块

@thonatos
Copy link
Member

thonatos commented Jan 3, 2019

之前基于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
Copy link
Member

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

@hugohua
Copy link

hugohua commented Aug 11, 2019

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

@ZQun
Copy link
Author

ZQun commented Aug 12, 2019

@hugohua 关注下midway-faas

@yugasun
Copy link

yugasun commented Jan 17, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants