【问题标题】:Debugging RCurl-based authentication & form submission调试基于 Curl 的身份验证和表单提交
【发布时间】:2014-03-11 04:27:45
【问题描述】:

SourceForge Research Data Archive (SRDA) 是我论文研究的数据源之一。我在调试以下与 SRDA 数据收集相关的问题时遇到了困难。

从 SRDA 收集数据需要身份验证,然后通过 SQL 查询提交 Web 表单。成功处理查询后,系统会生成一个带有查询结果的文本文件。在为 SRDA 数据收集测试我的 R 代码时,我更改了 SQL 请求以确保正在重新生成结果文件。但是,我发现文件内容保持不变(对应于先前的查询)。我认为文件内容缺乏刷新可能是由于身份验证查询表单提交失败。以下是代码(https://github.com/abnova/diss-floss/blob/master/import/getSourceForgeData.R)的调试输出:

make importSourceForge

Rscript --no-save --no-restore --verbose getSourceForgeData.R
running
  '/usr/lib/R/bin/R --slave --no-restore --no-save --no-restore --file=getSourceForgeData.R'

Loading required package: RCurl
Loading required package: methods
Loading required package: bitops
Loading required package: digest

Retrieving SourceForge data...

Checking request "SELECT *
FROM sf1104.users a, sf1104.artifact b
WHERE a.user_id = b.submitted_by AND b.artifact_id = 304727"...
* About to connect() to zerlot.cse.nd.edu port 80 (#0)
*   Trying 129.74.152.47... * connected
> POST /mediawiki/index.php?title=Special:Userlogin&action=submitlogin&type=login HTTP/1.1
Host: zerlot.cse.nd.edu
Accept: */*
Content-Length: 37
Content-Type: application/x-www-form-urlencoded

* upload completely sent off: 37out of 37 bytes
< HTTP/1.1 200 OK
< Date: Tue, 11 Mar 2014 03:49:04 GMT
< Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.25 with Suhosin-Patch
< X-Powered-By: PHP/5.2.4-2ubuntu5.25
* Added cookie wiki_db_session="c61...a3c" for domain zerlot.cse.nd.edu, path /, expire 0
< Set-Cookie: wiki_db_session=c61...a3c; path=/
< Content-language: en
< Vary: Accept-Encoding,Cookie
< Expires: Thu, 01 Jan 1970 00:00:00 GMT
< Cache-Control: private, must-revalidate, max-age=0
< Transfer-Encoding: chunked
< Content-Type: text/html; charset=UTF-8
<
* Connection #0 to host zerlot.cse.nd.edu left intact
[1] "Before second postForm()"
* Re-using existing connection! (#0) with host zerlot.cse.nd.edu
* Connected to zerlot.cse.nd.edu (129.74.152.47) port 80 (#0)
> POST /cgi-bin/form.pl HTTP/1.1
Host: zerlot.cse.nd.edu
Accept: */*
Cookie: wiki_db_session=c61...a3c
Content-Length: 129
Content-Type: application/x-www-form-urlencoded

* upload completely sent off: 129out of 129 bytes
< HTTP/1.1 500 Internal Server Error
< Date: Tue, 11 Mar 2014 03:49:04 GMT
< Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.25 with Suhosin-Patch
< Vary: Accept-Encoding
< Connection: close
< Transfer-Encoding: chunked
< Content-Type: text/html
<
* Closing connection #0
Error: Internal Server Error
Execution halted
make: *** [importSourceForge] Error 1

我尝试使用调试输出以及来自 Firefox 嵌入式开发者工具的网络协议分析器来解决这个问题,但到目前为止还没有取得多大成功。非常感谢任何建议和帮助。

更新:

if (!require(RCurl)) install.packages('RCurl')
if (!require(digest)) install.packages('digest')

library(RCurl)
library(digest)

# Users must authenticate to access Query Form
SRDA_HOST_URL  <- "http://zerlot.cse.nd.edu"
SRDA_LOGIN_URL <- "/mediawiki/index.php?title=Special:Userlogin"
SRDA_LOGIN_REQ <- "&action=submitlogin&type=login"

# SRDA URL that Query Form sends POST requests to
SRDA_QUERY_URL <- "/cgi-bin/form.pl"

# SRDA URL that Query Form sends POST requests to
SRDA_QRESULT_URL <- "/qresult/blekh/blekh.txt"

# Parameters for result's format
DATA_SEP <- ":" # data separator
ADD_SQL  <- "1" # add SQL to file

curl <<- getCurlHandle()

srdaLogin <- function (loginURL, username, password) {

  curlSetOpt(curl = curl, cookiejar = 'cookies.txt',
             ssl.verifyhost = FALSE, ssl.verifypeer = FALSE,
             followlocation = TRUE, verbose = TRUE)

  params <- list('wpName1' = username, 'wpPassword1' = password)

  if(url.exists(loginURL)) {
    reply <- postForm(loginURL, .params = params, curl = curl,
                      style = "POST")
    #if (DEBUG) print(reply)
    info <- getCurlInfo(curl)
    return (ifelse(info$response.code == 200, TRUE, FALSE))
  }
  else {
    error("Can't access login URL!")
  }
}


srdaConvertRequest <- function (request) {

  return (list(select = "*",
               from = "sf1104.users a, sf1104.artifact b",
               where = "b.artifact_id = 304727"))
}


srdaRequestData <- function (requestURL, select, from, where, sep, sql) {

  params <- list('uitems' = select,
                 'utables' = from,
                 'uwhere' = where,
                 'useparator' = sep,
                 'append_query' = sql)

  if(url.exists(requestURL)) {
    reply <- postForm(requestURL, .params = params, #.opts = opts,
                      curl = curl, style = "POST")
  }
}


srdaGetData <- function(request) {

  resultsURL <- paste(SRDA_HOST_URL, SRDA_QRESULT_URL,
                      collapse="", sep="")

  results.query <- readLines(resultsURL, n = 1)

  return (ifelse(results.query == request, TRUE, FALSE))
}


getSourceForgeData <- function (request) {

  # Construct SRDA login and query URLs
  loginURL <- paste(SRDA_HOST_URL, SRDA_LOGIN_URL, SRDA_LOGIN_REQ,
                    collapse="", sep="")
  queryURL <- paste(SRDA_HOST_URL, SRDA_QUERY_URL, collapse="", sep="")

  # Log into the system 
  if (!srdaLogin(loginURL, USER, PASS))
    error("Login failed!")

  rq <- srdaConvertRequest(request)

  srdaRequestData(queryURL,
                  rq$select, rq$from, rq$where, DATA_SEP, ADD_SQL)

  if (!srdaGetData(request))
    error("Data collection failed!")
}


message("\nTesting SourceForge data collection...\n")

getSourceForgeData("SELECT * 
FROM sf1104.users a, sf1104.artifact b 
WHERE a.user_id = b.submitted_by AND b.artifact_id = 304727")

# clean up
close(curl)

更新 2(无功能版本):

if (!require(RCurl)) install.packages('RCurl')
library(RCurl)

# Users must authenticate to access Query Form
SRDA_HOST_URL  <- "http://zerlot.cse.nd.edu"
SRDA_LOGIN_URL <- "/mediawiki/index.php?title=Special:Userlogin"
SRDA_LOGIN_REQ <- "&action=submitlogin&type=login"

# SRDA URL that Query Form sends POST requests to
SRDA_QUERY_URL <- "/cgi-bin/form.pl"

# SRDA URL that Query Form sends POST requests to
SRDA_QRESULT_URL <- "/qresult/blekh/blekh.txt"

# Parameters for result's format
DATA_SEP <- ":" # data separator
ADD_SQL  <- "1" # add SQL to file


message("\nTesting SourceForge data collection...\n")

curl <- getCurlHandle()

curlSetOpt(curl = curl, cookiejar = 'cookies.txt',
           ssl.verifyhost = FALSE, ssl.verifypeer = FALSE,
           followlocation = TRUE, verbose = TRUE)

# === Authentication ===

loginParams <- list('wpName1' = USER, 'wpPassword1' = PASS)

loginURL <- paste(SRDA_HOST_URL, SRDA_LOGIN_URL, SRDA_LOGIN_REQ,
                  collapse="", sep="")

if (url.exists(loginURL)) {
  postForm(loginURL, .params = loginParams, curl = curl, style = "POST")
  info <- getCurlInfo(curl)
  message("\nLogin results - HTTP status code: ", info$response.code, "\n\n")
} else {
  error("\nCan't access login URL!\n\n")
}

# === Data collection ===

# Previous query was: "SELECT * FROM sf0305.users WHERE user_id < 100"
query <- list(select = "*",
              from = "sf1104.users a, sf1104.artifact b",
              where = "b.artifact_id = 304727") 

getDataParams <- list('uitems'       = query$select,
                      'utables'      = query$from,
                      'uwhere'       = query$where,
                      'useparator'   = DATA_SEP,
                      'append_query' = ADD_SQL)

queryURL <- paste(SRDA_HOST_URL, SRDA_QUERY_URL, collapse="", sep="")

if(url.exists(queryURL)) {
  postForm(queryURL, .params = getDataParams, curl = curl, style = "POST")
  resultsURL <- paste(SRDA_HOST_URL, SRDA_QRESULT_URL,
                      collapse="", sep="")
  results.query <- readLines(resultsURL, n = 1)
  request <- paste(query$select, query$from, query$where)
  if (results.query == request)
    message("\nData request is successful, SQL query: ", request, "\n\n")
  else
    message("\nData request failed, SQL query: ", request, "\n\n")
} else {
  error("\nCan't access data query URL!\n\n")
}

close(curl)

更新 3(服务器端调试)

最后,我能够与负责系统的人员取得联系,他帮助我将问题缩小到 cookie 管理 恕我直言。这是错误日志记录,对应运行我的代码:

[2014 年 3 月 21 日星期五 15:33:14] [错误] [客户端 54.204.180.203] [3 月 21 日星期五 15:33:14 2014] form.pl: /tmp/sess_3e55593e436a013597cd320e4c6a2fac: 在 /var/www/cgi-bin/form.pl 第 43 行

以下是产生该错误的服务器端脚本Perl)的sn-p(脚本中的第1行是bash解释器指令,因此报告了行号43 很可能是第 44 行):

42     if (-e "/tmp/sess_$file") {
43     $session = PHP::Session->new($cgi->cookie("$session_name"));
44     $user_id = $session->get('wsUserID');
45     $user_name = $session->get('wsUserName');

以下是会话信息(1)认证后和(2)提交数据请求后,通过追踪获得手动身份验证和手动数据请求表单提交:

(1)“wiki_dbUserID=449;过期=周日,2014 年 4 月 20 日 21:04:14 GMT; 路径=/wiki_dbUserName=Blekh;过期=星期日,2014 年 4 月 20 日 21:04:14 GMT; 路径=/wiki_dbToken=已删除; expires=2013 年 3 月 21 日星期四 21:04:13 GMT"

(2) wiki_db_session=aaed058f97059174a59effe44b137cbc; _ga=GA1.2.2065853334.1395410153; EDSSID=e24ff5ed891c28c61f2d1f8dec424274; wiki_dbUserName=Blekh; wiki_dbLoggedOut=20140321210314; wiki_dbUserID=449

如果能帮我解决我的代码问题,我们将不胜感激!

【问题讨论】:

  • 您需要展示您的 R 代码 - 您如何确保在请求之间保留 cookie? (默认情况下,httr 会这样做,但 RCurl 不会)
  • @hadley:我在 GitHub 上提供了指向我的源代码的链接(就在输出之前)。
  • 那是很多代码。我建议将其删除为一个简单的可重现测试用例。
  • @hadley:根据您的建议,我正在用测试用例更新我的问题。它并没有我想要的那么小,但我尽了最大的努力来最小化和简化代码,而不会与我的现实生活场景有太大的偏差。我已经测试过了,结果是一样的。一件有趣的事情是我发现我的 SQL 请求中缺少空格(已修复),这应该触发了适当的 SQL 语法消息。对我来说,这意味着查询甚至没有达到被解析的程度。向您发送 USER 和 PASS,因为我不想危及对系统的访问。
  • @请忽略我关于 SQL 查询语法的注释 - 很好。

标签: r forms authentication web-scraping rcurl


【解决方案1】:

终于,终于,终于!我已经弄清楚是什么导致了这个问题,这让我非常头疼(比喻和字面意义上的)。它迫使我花费大量时间阅读各种互联网资源(包括许多 SO 问题和答案),调试我的代码并与人交流。我花了很多时间,但并没有白费,因为我学到了很多关于RCurl、cookie、Web 表单和HTTP 协议的知识。

原因似乎比我想象的要简单得多。虽然表单提交失败的直接原因与 cookie 管理有关,但根本原因是使用了错误的参数名称 (ID) 的身份验证表单域。这两对非常相似,只需要一个额外的字符就可以触发整个问题。

经验教训:在遇到问题时,尤其是涉及身份验证的问题时,非常重要的是多次检查所有名称和 ID,并非常仔细地确保它们与应该使用的名称和 ID 相符。感谢所有帮助或试图帮助我解决这个问题的人!

【讨论】:

    【解决方案2】:

    我进一步简化了代码:

    library(httr)
    
    base_url  <- "http://srda.cse.nd.edu"
    
    loginURL <- modify_url(
      base_url, 
      path = "mediawiki/index.php", 
      query = list(
        title = "Special:Userlogin", 
        action = "submitlogin",
        type = "login",
        wpName1 = USER,
        wpPasswor1 = PASS
      )
    )
    r <- POST(loginURL)
    stop_for_status(r)
    
    queryURL <- modify_url(base_url, path = "cgi-bin/form.pl")
    query <- list(
      uitems       = "user_name",
      utables      = "sf1104.users a, sf1104.artifact b",
      uwhere       = "a.user_id = b.submitted_by AND b.artifact_id = 304727",
      useparator   = ":",
      append_query = "1"
    )
    r <- POST(queryURL, body = query, multipart = FALSE)
    stop_for_status(r)
    

    但我仍然得到 500。我试过了:

    • 设置我在浏览器中看到的额外 cookie(wiki_dbUserID、wiki_dbUserName)
    • 将标头 DNT 设置为 1
    • 设置引用到http://srda.cse.nd.edu/cgi-bin/form.pl
    • 将用户代理设置为与 chrome 相同
    • 设置接受“text/html”

    【讨论】:

    • 感谢您的帮助!我希望无论版本如何,结果都是一样的。但是,我理解您隔离问题的意图。那么,考虑到我无法直接访问系统日志,您认为下一步是什么?
    • @AleksandrBlekh 如错误消息所述,向系统管理员发送电子邮件
    • 嗨,哈德利!最后,我联系了系统负责人并获得了所需的详细信息(请参阅更新 3)。如果您能对此进行查看并提供建议,将不胜感激!
    • @AleksandrBlekh 您可以尝试使用 httr 开发版中的 verbose() - 它提供了有关在 POST 正文中发送的内容的更多信息。这可能是问题所在。
    • 嗨,哈德利!感谢您的建议。我已经想通了这个问题。请看我的最终答案——你可能会对真正的原因感到惊讶。如果你能看看这个,那就太好了:stackoverflow.com/questions/22372758/…。对你来说应该是小菜一碟;-)。
    【解决方案3】:

    以下对场景(错误情况)进行说明。

    来自 W3C RFC 2616 - HTTP/1.1 规范:

    10.5 服务器错误 5xx

    以数字“5”开头的响应状态码表示在 服务器知道它有错误或无法做到的 执行请求。除了响应 HEAD 请求时, 服务器应该包含一个包含错误解释的实体 情况,以及它是暂时的还是永久性的。用户 代理应该向用户显示任何包含的实体。这些回应 代码适用于任何请求方法。

    10.5.1 500 内部服务器错误

    服务器遇到了一个意外情况,阻止了它 满足请求。

    我对第 10.5 段的解释是,它暗示应该对错误情况超出提供的错误情况更详细解释在第 10.5.1 段中。但是,我认识到状态代码 500(第 10.5.1 段)的消息很可能被认为是足够的。欢迎确认任何一种解释!

    【讨论】:

    • should 的意思是“它会很好但不是必需的”。因此,从协议的角度来看,即使是没有详细信息的普通 HTTP 消息也是好的......
    • @ServerHorror 我同意。谢谢你的澄清。
    猜你喜欢
    • 2010-11-20
    • 2011-10-24
    • 2019-05-17
    • 2014-12-13
    • 1970-01-01
    • 2012-07-12
    • 2018-06-08
    • 2015-02-06
    相关资源
    最近更新 更多