动态网页挑战
novakon2016/08/04软件综合 IP:广东

人们不断地争辩:A语言比B语言好,C框架比D框架好……所以我从PG的网站上借鉴了这样一个挑战。

这个挑战非常简单,任何编程语言都能参与。然后我们可以看看,在每一种语言中分别需要多少行代码。

请写一个程序,令某个url(比如 http://localhost:port/abc)返回带有一个输入框和一个按钮的一张网页。当用户按下按钮的时候,将得到第二张网页,上面有一个链接写着“请点这里”。当用户点击这个链接,他将进入第三个网页,上面写着用户在第一页的输入框中输入的内容。

第三页只允许显示用户在第一页中实际输入的内容,也就是说输入框中的内容不准通过url传递,即无法通过编辑url来改变第三页的内容。

这个挑战虽然看上去简单,但并非故弄玄虚。每天,无数的Web应用(包括博客、网店、论坛、邮件)都在不断地重复这类工作。

更重要的是,这个挑战不需要借助任何稀有的库;但凡可以进行TCP通信的语言,都可以完成这个挑战。

[修改于 8年5个月前 - 2016/08/04 13:03:56]

来自:计算机科学 / 软件综合
6
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
novakon 作者
8年4个月前 修改于 8年4个月前 IP:广东
824158

因为我本人是JS hacker, 就用JS做一次吧。

首先,我复制了radio的代码,并作了一些细微的重构。功能完全一致。

<code class="language-js">var http=require('http');
var express=require('express');
var session=require('express-session');
var bodyParser=require("body-parser");
var app = express();

app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({secret: '123456',name:'session',cookie: {maxAge: 3600000},resave: false,saveUninitialized: true}));

//tag generator
var tg = (name,content,props)=>`<${name} ${props||''}${content?'':' '}>${content||''}${content?'':''}`

var htmlWrap = (content)=>tg('html',tg('body',content))

var textInput = (name)=>tg('input',null,'name="'+name+'" type="text"')
var submit = tg('input',null,'type="submit"')

//form generator
var fg = (content)=>tg('form',content,'action="sub" method="post"')

var formHTML = htmlWrap(fg(textInput('t')+submit))
var clickHTML = htmlWrap(tg('a','请点这里','href="/text"'))

app.get('/', function(req, res) {
  res.send(formHTML);
});

app.post('/sub',function(req,res){
  req.session.text=req.body.t;
  res.send(clickHTML)
});

var textToHTML = (text)=>htmlWrap(text)

app.get('/text',function(req,res){
  res.send(textToHTML(req.session.text||'....'))
});

app.listen(9001,()=>{console.log('listening on 9001');});

</${name}></code>

但是我觉得:这不清真。表达仍然太冗长、太不自然了。我准备写个清真的版本。

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
novakon作者
8年4个月前 修改于 8年4个月前 IP:广东
824159
可以,终于清真了

采用了异步Promise写法:

<code class="language-javascript">//tag generator
var tg = (name,content,props)=>`<${name} ${props||''}${content?'':' '}>${content||''}${content?'':''}`

var htmlWrap = (content)=>tg('html',tg('body',content))

var textInput = (name)=>tg('input',null,'name="'+name+'" type="text"')
var submit = tg('input',null,'type="submit"')

//form generator
var fg = (content)=>tg('form',content,'action="" method="post"')

var formHTML = (content)=>htmlWrap(fg(content))
var linkHTML = (address)=>htmlWrap(tg('a','请点这里','href="'+address+'"'))
var textHTML = (text)=>htmlWrap(text)

var ee = (require('events').EventEmitter);

var nextVisitPromise = function(){
  var nvp = {}
  var e = new ee;
  nvp.trig = (req,res)=>{
    e.emit('trig',{req,res,waitForNextVisit:nvp.waitForNextVisit})
  }

  nvp.waitForNextVisit = ()=>{
    return new Promise((resolve,reject)=>{
      e.on('trig',resolve)
    })
  }
  return nvp
}

var nvprecords = {}

var bindOp = (path,op)=>{
  app.all(path,(req,res)=>{
    var s = req.session
    var key = 'p'+path

    if(s[key]){
      console.log(s[key]);
      var nvp = nvprecords[s.id]
      nvp.trig(req,res)
    }else{
      s[key] = true
      var nvp = nextVisitPromise()
      nvprecords[s.id]=nvp

      nvp.waitForNextVisit()
      .then((ctx)=>{
        return op(ctx)
      })
      .then(()=>{
        console.log('clearing session...');
        req.session.destroy()
        nvprecords[s.id]=undefined
      })

      nvp.trig(req,res)
    }
  })
}

app.listen(9001,()=>{console.log('listening on 9001');});

bindOp('/abc',(ctx)=>{
  var userinput
  ctx.res.send(formHTML(textInput('cde')+submit))
  return ctx.waitForNextVisit()
  .then(ctx=>{
    userinput = ctx.req.body.cde
    ctx.res.send(linkHTML('/abc'))
    return ctx.waitForNextVisit()
  })
  .then(ctx=>{
    ctx.res.send(textHTML(userinput||'....'))
  })
})

</${name}></code>

上面这段代码,访问/abc路径后,效果符合题目要求。 每次向用户返回HTML之后,直接Promise等待用户下一次访问,并通过闭包使用userinput变量。使用这种设计,就可以很短的代码,设计任意复杂的跳转流程,而不需要使用大量的路径以及状态机。

坏处:相当于把HTTP硬生生地变成了有状态协议,宕机的时候状态会丢失

好处:写起来无负担

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
novakon作者
8年4个月前 修改于 8年4个月前 IP:广东
824160

稍作修改,现在可以接受两个输入,并且在最后一页显示。

<code class="language-javascript">bindOp('/abc',(ctx)=>{
  var userinput
  ctx.res.send(formHTML(textInput('user')+textInput('pass')+submit))
  return ctx.waitForNextVisit()
  .then(ctx=>{
    userinput = ctx.req.body
    ctx.res.send(linkHTML('/abc'))
    return ctx.waitForNextVisit()
  })
  .then(ctx=>{
    ctx.res.send(textHTML(userinput.user+'\'s password is '+ userinput.pass||'....'))
  })
})
</code>
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
上级专业
同级专业
novakon
学者 机友 笔友
文章
1256
回复
8386
学术分
16
2008/03/29注册,2年10个月前活动

已走,勿送

主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}