这是一个技术难题。
需求是:
有一个收费的下载链接,但是是基于用户成功下载才能进行收费。

可是如何判断用户是否成功下载呢?

找了很多的技术文档,没有结果。其中常见的一种方案是:


  String file=request.getParameter("file");//物理文件路径
    String filename=file;
     if(file.startsWith("/"))
        file=session.getServletContext().getRealPath(file);
    if(file.indexOf("\\")>-1)
        filename=file.substring(filename.lastIndexOf("\\")+1);
    String err=null; String txt=null;
    System.out.println("filename: "+filename);
    byte data[]=null;
        try{
            InputStream inputStream = new FileInputStream(file); // 以byte流的方式打开文件 d:\1.gif
            int i=inputStream.available(); //得到文件大小
            data=new byte[i];
            inputStream.read(data); //读数据
            inputStream.close();
            inputStream=null;
        }catch(FileNotFoundException e){
            err="无法在服务器上获取相关文件!文件不存在.";
        }catch(Exception e){
            err=e.getMessage();
        }
     System.out.println(filename);
    if(data==null||err!=null){
        if(txt!=null)
           txt="请求的数据不是合法的二进制格式!";
        else{
            if(err==null)txt="无法从服务器获取相关文件,文件数据不存在或已经损坏.";
            else txt=err;
        }
        String msg="无法下载文件:"+filename+"\\n"+txt;
        return;
    }
    response.setContentType("application/octet-stream");
    response.addHeader("Content-disposition" , "attachment;filename="+filename+"\"");
    response.getOutputStream().write(data);
    response.getOutputStream().close();
    out.clear();
    out = pageContext.pushBody();
    data=null;                                                                 
    MD5 md=new MD5();
    String ip=request.getRemoteHost();
    System.out.println("下载成功!");
// 进行下载扣费


经过测试,发现这种方法不管客户端是否下载成功都会进行扣费,和


和下面的代码的执行效果是一样的

response.sendRedirect("dowloadfiel_url");
//进行扣费



但是我相信,WEB容器是肯定知道下载是否完成的,而且能够触发下载完成事件,否则,容器怎么知道什么时候停止向客户端发送数据流。


欢迎大家提供思路。
评论
fins 2007-11-05
rehte 写道
fins:
我觉得你使用socket直接进行编程和使用标准httpserver的结果是一样的。因为httpserver最终最底层也是使用socket实现的。

这点我当然清楚
但是显然楼主不清楚 ,所以我要解释给他听, 客户端断开连接 取消下载后, 服务端是可以知道的.

另外 ,我觉得你说这个和 楼主提的问题 没有什么关系
楼主的问题是
服务器端 如何 判断 客户端成功下载.

这里的问题 是 服务器端 与 客户端 ,是两台机器的事情.
如果非要把人的因素考虑进来 "他在浏览器提示保存时,取消了保存怎么办"
那我问你, 如果他保存了, 可是保存之后,才发现自己下载错了 ,又把文件删除了, 那服务端怎么知道?

楼主的问题是一个技术问题, 而楼上回答的 是一个 收费下载机制的实现方式问题.

我不知道楼主更需要的是哪个答案.

但是,我的观点是:
文件一旦下载到客户端, 那么就应该认为他下载成功了. 而他是否保存,是否删除,以及对他来说是否有用,我们无需关心 也无法正确判断.
毕竟互联网服务不是去超市买东西, 不包退不包换.

你打错电话了, 然后跟移动说, 我打错了, 刚才那电话的花费你别给我扣, 他们会答应吗????
rehte 2007-11-05
fins:
我觉得你使用socket直接进行编程和使用标准httpserver的结果是一样的。因为httpserver最终最底层也是使用socket实现的。退一步讲,使用socket能准确判知客户端浏览器是否下载成功,但是如果用户发现是误点链接造成的下载,他在浏览器提示保存时,取消了保存怎么办?因此还是不准确。只有在索取密码那种办法才能严格断定用户的行为:
1.他的确下载成功了,解压正常,但缺少密码。
2.他的确想使用该软件,如果是误点而实际并不想使用,他就不会索要密码了。要密码就证明了他想用。这儿密码的含义同购买软件serial number一样,具有确保软件真正是到达用户的作用。它间接的起到了证明用户成功获得软件的事实,证明了他主观上的确要想要下载该软件的,而不是误点造成的。
按照你们的做法,如果用户因为误点而被扣费,他们肯定会很生气,甚至会产生诉讼。
fins 2007-11-04
fireflyc 写道
fins同学好想忘记了一个问题,HTTP是无状态的。
服务器端发送一段数据(此处是文件)到浏览器,浏览器做相应的处理(此处是保存文件)在这个过程中浏览器只是被动的接受数据,如果在一段时间内没有接受到新的数据就会弹出无法下载的对话框(或者是不断的重试~~~~~)。在这一些行为中浏览器端始终是被动的,无状态的。这个问题从本质上是没有办法解决的。不过有一个非常不错的办法,上面的也有朋友提到了
可以在24小时内对一个账户重复下载一个资源进行一次扣费处理。
比如:
user1下载了一个资源rs1,按照常规的那样提示用户下载,然后扣费处理,如果下载成功也就完成任务了;如果下载失败用户可以重新下载rs1而不会被重复扣费,即使用户下载成功后也是可以重复下载的,(注意重复下载有一个有效时间比如24小时)这并不会造成什么损失(用户已经下载成功了,他重复下载等于说是重他硬盘上copy一份而已)。


楼主的问题核心是:
服务端 如何 判断 客户端 下载完成.

你说的"HTTP无状态 浏览器端始终是被动的,无状态的...." 和楼主的问题又什么关系吗???


你又说:
"然后扣费处理,如果下载成功也就完成任务了..."
楼主的问题就是, 你如何判断客户端"下载成功"????
fins 2007-11-04
kjava 写道


引用
但是就像我前面说的,有一个误差是无法避免的
就是服务器向客户端发送 最后一个字节 之后, 服务端就认为下载完毕,但是这最后一个字节是否传到了客户端 服务器无法知道,除非你再做个特殊的下载软件.


以上是JAVA实现的http server类,很显然,这种方式的误差,不是简单的在服务器发送完最后一个字节的误差,而是客户端在取消下载申请后,服务器断一样会认为客户端已经进行了下载,因为这种方式服务器端不知道客户端已经点击了取消下载按钮。

但是我认为服务器端应该是能够判断出客户端的下载情况的,不需要通过activex,因为那样的话太麻烦。


一个完整的 httpFileserver 应该由自己来启动socket监听的
浏览器 通过socket连接到 httpFileserver , 浏览器断开后(取消下载,非法关闭等), 服务器是可以监听到的

你找的这个例子不能说明问题.


其实楼主举的这个例子 也能说明一些问题

你看那个 HttpOutputStream out

他实际上就是从 Socket.getOutputStream()的来的

如果这时候 客户端的 Socket断开时, out.write (in); 是会抛异常的
很明显的啊.
所以 服务端是可以知道 客户端是否已经下载完成的(除了我所说的那个误差).

请看下面的代码:
byte[] buff = new byte[2048];
while (true){
  int read = data.read( buff, 0, buff.length );
  if (read <= 0)
  break;
  out.write( buff, 0, read );
}
out.flush();
out.close();
// 此时 理解为 客户端成功下载完.

下载过程中,如果客户端中断,会抛出异常.你只要拦截处理一下就可以.

而如果顺利的执行完了 while循环,并且close了你就可以认为客户端下载完成了.
实际上,这时候一点误差都不会有, 因为你时在flush和close之后判断的.
如果你服务端不想主动flush 和 close,那么有可能存在2K的误差.
因为你最后一次执行out.write之后,有可能数据没有送到客户端.

只要稍微了解一下 HTTP SOCKET TCP/IP 的基础知识
你就应该能够理解我说的意思了 .

还有一点很重要:
我说的这个方案 是我自己亲自动手实践过的 绝对可行的.

最后再提醒一下楼主:
一个对下载 上传 要求严格的网站 ,必须要有一个很好的 http file server.
httpfile server 一个server级的东西,
而不是一个运行在某j2ee server下的servlet 或web listener.
fireflyc 2007-11-04
fins同学好想忘记了一个问题,HTTP是无状态的。
服务器端发送一段数据(此处是文件)到浏览器,浏览器做相应的处理(此处是保存文件)在这个过程中浏览器只是被动的接受数据,如果在一段时间内没有接受到新的数据就会弹出无法下载的对话框(或者是不断的重试~~~~~)。在这一些行为中浏览器端始终是被动的,无状态的。这个问题从本质上是没有办法解决的。不过有一个非常不错的办法,上面的也有朋友提到了
可以在24小时内对一个账户重复下载一个资源进行一次扣费处理。
比如:
user1下载了一个资源rs1,按照常规的那样提示用户下载,然后扣费处理,如果下载成功也就完成任务了;如果下载失败用户可以重新下载rs1而不会被重复扣费,即使用户下载成功后也是可以重复下载的,(注意重复下载有一个有效时间比如24小时)这并不会造成什么损失(用户已经下载成功了,他重复下载等于说是重他硬盘上copy一份而已)。
kjava 2007-11-04
/*
 * Java Network Programming, Second Edition
 * Merlin Hughes, Michael Shoffner, Derek Hamner
 * Manning Publications Company; ISBN 188477749X
 *
 * http://nitric.com/jnp/
 *
 * Copyright (c) 1997-1999 Merlin Hughes, Michael Shoffner, Derek Hamner;
 * all rights reserved; see license.txt for details.
 */

import java.io.*;

public class HttpFile implements HttpProcessor {
  protected File file;
  
  public HttpFile (HttpInputStream in) throws IOException {
    if (in.getMethod () == HTTP.METHOD_POST)
      throw new HttpException (HTTP.STATUS_NOT_ALLOWED,
                               "<TT>" + in.getMethod () + " " + in.getPath () + "</TT>");
    file = new File (HTTP.HTML_ROOT, HTTP.translateFilename (in.getPath ()));
    if (in.getPath ().endsWith ("/"))
      file = new File (file, HTTP.DEFAULT_INDEX);
    if (!file.exists ())
      throw new HttpException (HTTP.STATUS_NOT_FOUND,
                               "File <TT>" + in.getPath () + "</TT> not found.");
    if (file.isDirectory ())
      throw new RedirectException (HTTP.STATUS_MOVED_PERMANENTLY,
                                   in.getPath () + "/");
    if (!file.isFile () || !file.canRead ())
      throw new HttpException (HTTP.STATUS_FORBIDDEN, in.getPath ());
  }

  public void processRequest (HttpOutputStream out) throws IOException {
    out.setHeader ("Content-type", HTTP.guessMimeType (file.getName ()));
    out.setHeader ("Content-length", String.valueOf (file.length ()));
    if (out.sendHeaders ()) {
      FileInputStream in = new FileInputStream (file);
      out.write (in);
      in.close ();
    }
  }
}


引用
但是就像我前面说的,有一个误差是无法避免的
就是服务器向客户端发送 最后一个字节 之后, 服务端就认为下载完毕,但是这最后一个字节是否传到了客户端 服务器无法知道,除非你再做个特殊的下载软件.


以上是JAVA实现的http server类,很显然,这种方式的误差,不是简单的在服务器发送完最后一个字节的误差,而是客户端在取消下载申请后,服务器断一样会认为客户端已经进行了下载,因为这种方式服务器端不知道客户端已经点击了取消下载按钮。

但是我认为服务器端应该是能够判断出客户端的下载情况的,不需要通过activex,因为那样的话太麻烦。
fins 2007-11-04
这个问题 最好的解决方案就是自己写个简单的httpFile Server这个不难
网上有很多实现,简单的只要一个类就搞定

这样你就可以处理很多事情了

但是就像我前面说的,有一个误差是无法避免的
就是服务器向客户端发送 最后一个字节 之后, 服务端就认为下载完毕,但是这最后一个字节是否传到了客户端 服务器无法知道,除非你再做个特殊的下载软件.
renavatior 2007-11-04
这个可以用AJAX实现的吧,当下载完成它会触发一个事件这个时候提醒服务器端扣费
rehte 2007-11-04
我认为在不使用ActiveX等客户端下载软件的情况下,使用浏览器链接下载,可能只有上面那位老兄提的下载再索取密码扣费的方式是最准确的,甚至也只有那种办法才能确保用户真正的得到你的软件。
kjava 2007-11-03
引用
关键是“客户端下载是否完成”这个怎么理解?是不是当用户把文件完全保存到本地硬盘之后才算下载完成?


不错,客户端下载完成的含义就是用户把文件保存到本地硬盘。
之所以进行这样的定义,是为了更准确地判断出,该点击用户确实是想下载该文件的用户,而不是被误导或是其他不正常原因而导致点击了下载链接。因为后面紧跟着扣费操作!

如果用户仅仅是点击了下载连接,而取消了下载确认对话框,那么显然,这个用户对该文件不感兴趣,扣费操作是不应该执行的。

而且,我想知道的是,如果不能准确判断用户是否下载了文件,为什么要用一大堆的代码去 读文件,发文件,而不是用一条 response.sendForward(url),效果不是一样的吗?
someone 2007-11-02
fins 写道
客户端 发送请求
服务端得到请求
服务端去读文件
服务端将读到的文件发送给客户端
服务端读取并发送完全部文件后,可以理解为客户端已经完成下载.
如果这期间客户端 中止下载, 那么服务端可以得到客户端的链接断开的信息

当然 这个思路是有误差的, 误差发生在最后时刻.
服务端"读取并发送完全部文件",但是客户端还没有接收到最后发送的那一部分内容时,会出现一点点误差,但是应该可以忽略不计的.


关键是“客户端下载是否完成”这个怎么理解?是不是当用户把文件完全保存到本地硬盘之后才算下载完成?

对于Web系统Download文件,是不可能在服务器端完全准确地判断文件是否Download成功了。浏览器都有buffer,对比较小的文件,当OutputStream().write已经结束,也就是数据已经完全发送到客户端,这个时候HttpConnect会关闭,一次Http会话结束,而这时客户端会弹出保存文件的对话框,当客户选择Cancel的时候,服务器端已经完全不知道了。

所以“跟踪 客户端下载是否完成”是无法实现的,除非你自己做ActiveX来下载。
kjava 2007-11-02
to fins

引用
客户端 发送请求
服务端得到请求
服务端去读文件
服务端将读到的文件发送给客户端
服务端读取并发送完全部文件后,可以理解为客户端已经完成下载.
如果这期间客户端 中止下载, 那么服务端可以得到客户端的链接断开
的信息

当然 这个思路是有误差的, 误差发生在最后时刻.
服务端"读取并发送完全部文件",但是客户端还没有接收到最后发送的那一部分内容时,会出现一点点误差,但是应该可以忽略不计的.


    response.setContentType("application/octet-stream");  
    response.addHeader("Content-disposition" , "attachment;filename="+filename+"\"");  
    while(发送文件大小完成)
    { 
    response.getOutputStream().write(data1);   
    response.getOutputStream().write(data2);

    .......
    }
    扣费操作.....
   
    
  
    response.getOutputStream().close();  



以上的代码思路是不正确的,response.getOutputStream().write();写的数据其实还在服务器端,请求下载文件的触发请求 与 相应文件输出的下载流的操作 应该不是同一个线程,所以,以上的思路在下载过程中就会进行扣费。
myy 2007-11-01
lszone 写道
myy 写道
服务端判断不太好做,也很不可靠。

建议这样:

将收费的东西打包压缩并加密码(最好能动态压缩),让别人随便下,但是没法直接用,然后用户必须点击页面的“扣费”按钮,进行扣费,才给出密码(也可以通过邮件发到用户邮箱中)


典型的不考虑产品的做法,对于一些方案总想绕道而行,你这种做法无疑是提高了操作的复杂性


很多共享软件就是这样做的,人家不是做产品么?

再说了,为什么要有麻烦的https,不就是因为http不安全绕道而行么?

“提高了操作的复杂性”是在有限条件下,需求要求过高 所必须付出的代价。
lszone 2007-11-01
myy 写道
服务端判断不太好做,也很不可靠。

建议这样:

将收费的东西打包压缩并加密码(最好能动态压缩),让别人随便下,但是没法直接用,然后用户必须点击页面的“扣费”按钮,进行扣费,才给出密码(也可以通过邮件发到用户邮箱中)


典型的不考虑产品的做法,对于一些方案总想绕道而行,你这种做法无疑是提高了操作的复杂性
bearlz 2007-11-01
我以前在一家新闻图片销售公司做的时候,记录客户第一次下载的时间,生成订单,然后在一段时间内是免费下载的。
控制客户端感觉比较难
timerri 2007-11-01
try{
response.getOutputStream().write(data);
response.getOutputStream().close();
}catch(e ...){
//失败
}

其实,这样就足够了,如果还是出问题,那就肯定是客户端的问题。

你们其实设计思路有问题,应该不是如何保证全部正确发送,这基本是做不到的,因为你不能保证客户端的正确与否,而是要保证客户在出现问题后还能有补救的办法,比如再让他能免费下一次。
myy 2007-11-01
[quote="kjava"]to myy

[quote]try{
InputStream inputStream = new FileInputStream(file); // 以byte流的方式打开文件 d:\1.gif
int i=inputStream.available(); //得到文件大小
data=new byte[i];
inputStream.read(data); //读数据

//
// 带密码压缩数据
//


不知道这段怎么理解?

to

inputStream.close();
inputStream=null;

}catch(FileNotFoundException e){
err="无法在服务器上获取相关文件!文件不存在.";
}catch(Exception e){
err=e.getMessage();
} [/quote]


这不好理解么?

数据是服务端输出的,在输出前,压缩并加密了一下而已,比如简单一点可以这样:

1.用户 "user01" 请求文件"thedoc.doc"(如: http://.../getfile.jsp?file=thedoc.doc )
2.getfile.jsp先找到真正的thedoc.doc,并随机生成一个密码"asdfgh"(规则自定义)
3.getfile.jsp用命令行带参数(包括密码"asdfgh")调用rar.exe, 生成临时文件 user01_20071101100505_thedoc.doc.rar(规则自定义)
4.将文件名"user01_20071101100505_thedoc.doc.rar" 和 密码 "asdfgh"存入数据库
5.将文件 user01_20071101100505_thedoc.doc.rar 输出到用户

...

6.用户下载完成,用 winrar 打开(利用 winrar 验证文件是否完整),发现要密码才能解压,于是必须点“扣费”按钮(同时输入文件名"user01_20071101100505_thedoc.doc.rar"), 后台从数据库根据文件名,取出密码 "asdfgh"显示给用户。
smyl9989 2007-10-31
我怀疑你这个地方有没有try一下

response.getOutputStream().write(data);
response.getOutputStream().close();

还有什么原因会导致客户端还没下完就出故障?突然停电?这是客户端的问题,这个难道还得你们负责啊
ddandyy 2007-10-31
文件大约有多大........ 可以用下载软件下载么......
fins 2007-10-31
客户端 发送请求
服务端得到请求
服务端去读文件
服务端将读到的文件发送给客户端
服务端读取并发送完全部文件后,可以理解为客户端已经完成下载.
如果这期间客户端 中止下载, 那么服务端可以得到客户端的链接断开的信息

当然 这个思路是有误差的, 误差发生在最后时刻.
服务端"读取并发送完全部文件",但是客户端还没有接收到最后发送的那一部分内容时,会出现一点点误差,但是应该可以忽略不计的.
kjava
搜索本博客
我的相册
2b9a03cb-71f0-32db-afc5-10ea282d0465-thumb
DSC02765
共 1 张
最近加入圈子
存档
最新评论