安卓课程表(解决利用Httpclient登录获得cookie继续访问但网页仍提示无权限请登录的问题)

          第一次写CSDN博客,想一想还是有点小紧张。嘿嘿,希望能帮到和我一样刚入门的菜鸟coder解决特定问题。如果有幸被各路大牛看到此文,还望不吝赐教,指正文章中可能存在的错误。 

       安卓课程表的最主要目的是将教务网中的课程表信息(后面称之为“数据”)获取到,再显示于安卓应用界面上。需要解决的问题无非只有两个:1. 获得“数据”;2. 将“数据”显示在设计好的界面上。本文主要说如何获得“数据”。

       在浏览器上看到自己课程表需要如下两个过程:

       1. 登录(下图是学校教务网登录界面):


       2.点击“本学期选课”,获得课程表信息


       安卓课程表就是模拟浏览器这一行为,从教务网服务器上获取到“数据”。如下图:

       登录界面:

       

        获取课程表信息界面:


       最开始,我以为要实现类似“超级课程表”这样的应用需要像新浪微博API那样进行Oauth2认证,才能获得微博上的指定数据的JSON或XML格式,自己再去解析。这样岂不是需要向教务网索要接口等等。。。后来参考这篇博文:http://blog.csdn.net/u010858238/article/details/9029653(建议可以先直接去看这篇文章,如果看完后,具体实现中遇到问题,再回头来看我写的,可能我们遇到的问题一模一样。后文也全是在假设已经看过这篇文章的基础上写的),发现利用Httpclient,这个过程无非就是在安卓系统上仿浏览器获得数据的原理来实现,而且这样对于任何有网页端而无客户端的东西都可以用类似的方法去实现某一网站的客户端程序。

       我参考的那篇文章已经非常清楚地教给大家原理,而我之所以再写一篇文章的原因是:在自己实现安卓课程表的过程中,会遇到非常多意想不到的问题,而且整个过程并不是参考的那一篇文章所能解决的,原理性的东西只是起参考作用,实践过程中需要自己去解决很多问题,比如:为什么获得了cookie但总是提示无权限访问,请先登录?为什么一登录程序就停止运行?

       我在实现应用的过程中遵循下面的思路:

       步骤1. 利用Httpclient登录教务网,获得可以继续访问网站后续功能的可以作为“凭证”的cookie;

       步骤2. 带着这个cookie继续访问获得课程表信息(后面称之为“数据”);

       步骤3. 会发现步骤2中获得的“数据”其实是课程表页面的HTML文件(利用谷歌浏览器,在课程表网页点击右键审查元素看到的代码),利用JSoup解析HTML文件获得需要的指定信息,将之存储于SQlite数据库中,前端界面读取数据并显示在listview中(根据实际显示界面而定)。

       在步骤1中,我最开始陷入了误区,以为只要获得cookie就可以继续访问,但实际情况是:获得cookie并开始执行步骤2时总是提示无权限访问,请先登录。对这个问题,谷歌了几乎所有与Httpclient有关的博文,英文帮助文档,看完了网上关于HTTP协议的各种讲解后,发现坑不在这里,而在学校教务网与自己的理解上。对于cookie,我最初以为只有登录成功后,服务器才会给客户端一个供后续访问的凭证cookie,而实际情况是,即使登录失败,服务器也会给客户端一个cookie,只是这个cookie是没有权限的,无法继续访问后续只有登录了才能访问的界面。说白了,这个cookie对我们没用。发现这个问题的过程是:最初我用程序获得cookie,并带着这个cookie访问后续网页,发现无权限。后来,我先在谷歌浏览器上登录教务网,然后在登录成功的页面上点击审查元素,找到左上角的Network并点击,按F5刷新,重新载入页面。点击需要的元素,此处我需要index.jsp这个页面。如下图:


        右边Headers下Request Headers中的Cookie字段的JSESSIONID正是我们所需要的。我将这个值直接传入程序,发现成功获取到课程表信息的HTML文件,也就是说,我用程序获得的cookie只是一个无权限的cookie,只要我获得一个有权限的cookie就可以成功解决问题。

        所以,当利用Httpclient登录获得cookie继续访问但网页仍提示无权限请登录时,应该首先考虑是否获得了一个有权限的cookie?

        之后,我再次从浏览器上登录,这才发现了问题所在:原来学校教务网在登录界面(http://***.***.***.***/service/login.jsp)上提交表单数据后会先将数据交给servlet处理,再转发到一个新页面。而我正是忽略了这个界面,如下图:


        困扰了这么久,原来是urlLogin写错了,我错误地写成了登录界面的URL(http://***.***.***.***/service/login.jsp),正确的应该是http://***.***.***.***/servlet/UserLoginSQLAction,因为我需要将表单数据提交给它。这一点觉得学校教务网在设计上有点奇怪,调试的过程中我发现,即使登录失败,返回的HttpStatusCode仍是200,最开始也误导我,让我一直以为我登录成功了,根本没把问题归结在我其实就没登录成功上。

        在步骤3中,由于既有网络操作,又有数据库操作,所以需要将这些耗时的操作放在子线程中,而安卓系统不允许子线程更新UI线程,所以,像弹出Toast这样的提示框需要用到Handler(网上一搜一大堆教程)。我的做法是:将步骤1登录的过程放到一个线程当中,将获取课程表信息并存放“数据”到数据库的过程放到一个线程当中,利用join()方法控制线程执行的顺序,在成功执行完后,向Handler发消息,让Handler配合主线程更新UI,弹出Toast提示框。相关代码如下:

定义必要的全局变量:

	private static HttpClient httpclient = new DefaultHttpClient();// 创建一个客户端实体
	public static UserLoginInfo userLoginInfo = new UserLoginInfo();// 创建一个用户实体
	private static String urlLogin = "http://***.***.***.***/servlet/UserLoginSQLAction";// 登录URL
	private static String urlCourse = "http://***.***.***.***/student/course/MyCourseThisTerm.jsp";// 课程信息URL
	private String CourseHTML = null;// 保存获得的课程表网页HTML文件的String类型
        private static List<Cookie> cookies; // 保存登陆成功后的cookie
        private static ArrayList<CourseInfo> allCourse;// 所有课程
登录子线程:

class PostThread extends Thread {
		String Tuser_id;
		String Tpassword;

		public PostThread(String user_id, String password) {
			this.Tuser_id = user_id;
			this.Tpassword = password;
		}

		public void run() {
			HttpPost httpPost = new HttpPost(urlLogin);// Post方法
			HttpResponse httpResponseForPost = null;// Post方法的响应信息
			List<NameValuePair> params = new ArrayList<NameValuePair>();

			userLoginInfo.setUser_id(Tuser_id);// 学号
			userLoginInfo.setPassword(Tpassword);// 密码
			userLoginInfo.setUser_style("modern");// 浏览器上的界面风格,可要可不要
			userLoginInfo.setUser_type("student");// 用户类型

			/* 利用Post方法登录,目的是为了获取能继续访问网页的“通行证”cookie(登陆成功后的cookie) */
			/* Post方法参数设置 */
			params.add(new BasicNameValuePair("Browser", ""));
			params.add(new BasicNameValuePair("btn1", ""));
			params.add(new BasicNameValuePair("OperatingSystem", ""));
			params.add(new BasicNameValuePair("password", userLoginInfo
					.getPassword()));
			params.add(new BasicNameValuePair("url", "../usersys/index.jsp"));
			params.add(new BasicNameValuePair("user_id", userLoginInfo
					.getUser_id()));
			params.add(new BasicNameValuePair("user_style", userLoginInfo
					.getUser_style()));// 浏览器上的界面风格,可要可不要
			params.add(new BasicNameValuePair("user_type", userLoginInfo
					.getUser_type()));

			/*
			 * 设置必要的request header:Host头域指定请求资源的Intenet主机和端口号,
			 * 必须表示请求url的原始服务器或网关的位置。 HTTP/1.1请求必须包含主机头域,否则系统会以400状态码返回。
			 */
			httpPost.setHeader("Host", "***.***.***.***");
			/*
			 * 设置必要的request header:Referer 头域允许客户端指定请求uri的源资源地址,
			 * 这可以允许服务器生成回退链表,可用来登陆、优化cache等。 他也允许废除的或错误的连接由于维护的目的被 追踪。
			 * 如果请求的uri没有自己的uri地址,Referer不能被发送。 如果指定的是部分uri地址,则此地址应该是一个相对地址。
			 * 参考自http://blog.csdn.net/rainysia/article/details/8131174
			 */
			httpPost.setHeader("Referer",
					"http://***.***.***.***/service/login.jsp?user_type=student");
			try {
				httpPost.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
				httpResponseForPost = httpclient.execute(httpPost);
				if (httpResponseForPost.getStatusLine().getStatusCode() == 200) {
					cookies = ((AbstractHttpClient) httpclient)
							.getCookieStore().getCookies();

					// 利用SharedPreferences存储用户登录信息
					SharedPreferences userSharedPreferences = getSharedPreferences(
							"user", Activity.MODE_PRIVATE);
					SharedPreferences.Editor editor = userSharedPreferences
							.edit();

					editor.putString("user_id", userLoginInfo.getUser_id());

					editor.commit();

					/*Message msg = new Message();
					msg.what = 1;
					handler.sendMessage(msg);*/
				} else {
					/*Message msg = new Message();
					msg.what = 2;
					handler.sendMessage(msg);*/
				}
			} catch (ClientProtocolException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {

			}
		}
	}
获取课程表信息子线程:

class getThread extends Thread {
		String GTuser_id;

		public getThread(String user_id) {
			this.GTuser_id = user_id;
		}

		public void run() {
			HttpGet httpGet = new HttpGet(urlCourse);// Get方法
			HttpResponse httpResponseForGet = null;// Get方法的响应信息
			/* 利用Get方法,带上成功登录获得的cookie,继续访问课程表页面,并将页面HTML格式全部放在String中 */
			httpGet.setHeader("Cookie",
					"JSESSIONID=" + cookies.get(0).getValue() + "; user_id="
							+ userLoginInfo.getUser_id() + "; user_type="
							+ userLoginInfo.getUser_type() + "; user_style="
							+ userLoginInfo.getUser_style() + ";");
			try {
				httpResponseForGet = httpclient.execute(httpGet);
				if (httpResponseForGet.getStatusLine().getStatusCode() == 200) {
					StringBuffer stringBuffer = new StringBuffer();
					HttpEntity entity = httpResponseForGet.getEntity();
					InputStream inputStream = null;
					inputStream = entity.getContent();
					BufferedReader br = null;
					br = new BufferedReader(new InputStreamReader(inputStream,
							"UTF-8"));
					String data = "";
					while ((data = br.readLine()) != null) {
						stringBuffer.append(data);
					}
					CourseHTML = stringBuffer.toString();
				}
			} catch (ClientProtocolException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				// httpGet.releaseConnection();
			}
			// 利用Jsoup解析获得的HTML文件,可以忽略,因具体实现而异
			int flag = 1, idIndex = 4, nameIndex = 1, teacherIndex = 4, infoIndex = 1;
			if ("".equals(CourseHTML) && null == CourseHTML) {
				System.out.println("HTML null");
			}
			Document document = Jsoup.parse(CourseHTML);
			Elements courseId = document.select("td[width=8%]");
			Elements courseName = document.select("td[width=17%]");
			Elements courseTeacher = document.select("td[width=6%]");
			Elements courseInfo = document.select("td[width=20%]");
			Elements userName = document.select("td[height=32]");

			userLoginInfo.setUser_name(userName.text());
			ArrayList<String> id = getElement(courseId.text());
			ArrayList<String> name = getElement(courseName.text());
			ArrayList<String> teacher = getElement(courseTeacher.text());
			ArrayList<String> info = getElement(courseInfo.text());
			int idSize = 0,nameSize = 0,teacherSize = 0,infoSize = 0;
			idSize = id.size();
			nameSize = name.size();
			teacherSize = teacher.size();
			infoSize = info.size();
			while (flag != 0) {
				CourseInfo course = new CourseInfo();
				course.setCourseId(id.get(idIndex).toString());
				course.setCourseName(name.get(nameIndex).toString());
				course.setCourseTeacher(teacher.get(teacherIndex).toString());
				course.setCourseInfo(info.get(infoIndex).toString()
						+ info.get(infoIndex + 1).toString());
				allCourse.add(course);
				idIndex += 4;
				nameIndex += 1;
				teacherIndex += 3;
				infoIndex += 2;
				if (idIndex > idSize || nameIndex > nameSize || teacherIndex > teacherSize
|| infoIndex > infoSize) {
				flag = 0;
}
			}
			for (int i = 0; i < allCourse.size(); i++) {
				StringBuffer stringBuffer = new StringBuffer();
				stringBuffer.append(allCourse.get(i).getCourseTeacher() + "\n")
						.append(allCourse.get(i).getCourseInfo());
				courseDataBase.insertCourse(course_id, GTuser_id, allCourse
						.get(i).getCourseName(), stringBuffer.toString());
				Intent intent = new Intent();
				intent.setAction("action.refreshFriend");
				sendBroadcast(intent);
				showCountId();
			}
			/*Message msg = new Message();
			msg.what = 3;
			handler.sendMessage(msg);*/
		}
	}

        贴的代码中包含的很多自定义的方法没有列出具体代码。


github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章